ee)
=
j=}
oO
3°)
19°)
=
>
co)
7)
jo}
=>
=:
=F
19>)
co
om
=a
Cc
wn
a
a
Pa
wm
4
=|
5S)
o
ns
=
=|
i=}
=)
=)
a=)
19°)
ae J
rob)
ee
=)
co)
wm
as
n
lon
19°)
S|
n
=]
oO
a
a
=}
rot)
=]
(Sh
=|
—
3°)
=|
3°)
=]
+
rot)
=
Oo
=}
aS
Pj
9
S
S
S
A)
a]
B®
ASP)
isp)
ict
=}
ist)
Q
int}
S
sy
S
ima
A)
=
im
A>)
ND
S
el
S
Sponsored by
USENIX
in cooperation with
ACM SIGOPS
9th USENIX Symposium
on Operating Systems
SST!
TU Cele
(OSDI ‘10)
Vancouver, BC, Canada
October 4-6, 2010
© 2010 by The USENIX Association
All Rights Reserved
This volume is published as a collective work. Rights to individual papers
remain with the author or the author’s employer. Permission is granted for
the noncommercial reproduction of the complete work for educational or
research purposes. USENIX acknowledges all trademarks herein.
ISBN 978-1-931971-79-9
USENIX Association
Proceedings of the
9th USENIX Symposium on Operating
Systems Design and Implementation
(OSDI 710)
October 4—6, 2010
Vancouver, BC, Canada
Symposium Organizers
Program Co-Chairs
Remzi Arpaci-Dusseau, University of Wisconsin,
Madison
Brad Chen, Google, Inc.
Program Committee
Dave Andersen, Carnegie Mellon University
Emery Berger, University of Massachusetts Amherst
Felipe Cabrera, Amazon.com
George Candea, EPFL
Bryan Cantrill, Sun Microsystems, Inc.
Pei Cao, Google, Inc.
Robert M. English, Facebook, Inc.
Bryan Ford, Yale University
Michael J. Freedman, Princeton University
Kim Hazelwood, University of Virginia
Jon Howell, Microsoft Research
Wilson Hsieh, Google, Inc.
Michael Isard, Microsoft Research
Brad Karp, University College London
Randy Katz, University of California, Berkeley
Sam King, University of Illinois, Urbana-Champaign
Hank Levy, University of Washington
Shan Lu, University of Wisconsin, Madison
Ed Nightingale, Microsoft Research
Christopher Olston, Yahoo! Research
Adrian Perrig, Carnegie Mellon University
Vijayan Prabhakaran, Microsoft Research
Mendel Rosenblum, Stanford University
Jiri Schindler, NetApp, Inc.
Bianca Schroeder, University of Toronto
Emin Gin Sirer, Cornell University
Amin Vahdat, University of California, San Diego
Carl Waldspurger, VMware
Emmett Witchel, University of Texas, Austin
Jay Wylie, HP Labs
Junfeng Yang, Columbia University
Nickolai Zeldovich, Massachusetts Institute of
Technology
Lidong Zhou, Microsoft Research
Steering Committee
Richard Draves, Microsoft Research
Margo Seltzer, Harvard School of Engineering and
Applied Sciences
Robbert van Renesse, Cornell University
Ellie Young, USENIX
Poster Session Chair
Jon Howell, Microsoft Research
Research Vision Session Program
Committee
Sam King (Chair), University of Illinois, Urbana-
Champaign
Shan Lu, University of Wisconsin—Madison
Emmett Witchel, University of Texas, Austin
The USENIX Association Staff
External Reviewers
Ole Agesen
William de Bruijn
Haowen Chan
Anthony Cozzie
Azadeh Farzan
Ariel Feldman
Prem Gopalan
Collin Jackson
Eyal de Lara
Wyatt Lloyd
Tim Mann
Jim Mattson
David Maziéres
David Meisner
Bryan Parno
Ryan Peterson
Donald E. Porter
David Shue
Ahren Studer
Shuo Tang
Amit Vasudevan
Arun Venkataramani
Bernard Wong
Hui Xue
Ting Yang
Cristian Zamfir
Steve Zdancewic
9th USENIX Symposium on Operating Systems Design and Implementation
October 4—6, 2010
Vancouver, BC, Canada
Message from the Program Co-Chairs. ... 0.00... eee n etn eet eee ees vil
Monday, October 4
Kernels: Past, Present, and Future
An Analysis of Linux Scalability to Many Cores ...... 0... cece ett n ene eee e eee 1
Silas Boyd-Wickizer, Austin T: Clements, Yandong Mao, Aleksey Pesterev, M. Frans Kaashoek, Robert Morris,
and Nickolai Zeldovich, MIT CSAIL
Trust and Protection in the Illinois Browser Operating System ......... 00... c ect n eee 17
Shuo Tang, Haohui Mai, and Samuel T. King, University of Illinois at Urbana-Champaign
FlexSC: Flexible System Call Scheduling with Exception-Less System Calls....... 00.0... cee eee eee eee eee 33
Livio Soares and Michael Stumm, University of Toronto
Inside the Data Center, 1
Finding a Needle in Haystack: Facebook’s Photo Storage. ...... 0... 0c cece ene eee eens 47
Doug Beaver, Sanjeev Kumar, Harry C. Li, Jason Sobel, and Peter Vajgel, Facebook Inc.
Availability in Globally Distributed Storage Systems. ...... 00.0... e nets 61
Daniel Ford, Francois Labelle, Florentina I. Popovici, Murray Stokely, Van-Anh Truong, Luiz Barroso, Carrie
Grimes, and Sean Quinlan, Google, Inc.
Nectar: Automatic Management of Data and Computation in Datacenters ........ 2.0... c cee eee eee ene 75
Pradeep Kumar Gunda, Lenin Ravindranath, Chandramohan A. Thekkath, Yuan Yu, and Li Zhuang, Microsoft
Research Silicon Valley
Security Technologies
Intrusion Recovery Using Selective Re-execution. .... 0.0... cece eee eee teen cnet beeen ene 89
Taesoo Kim, Xi Wang, Nickolai Zeldovich, and M. Frans Kaashoek, MIT CSAIL
Static Checking of Dynamically-Varying Security Policies in Database-Backed Applications................ 105
Adam Chlipala, Impredicative LLC
Accountable Virtual Machine$.3.¢. 0.4.0.0 cc geese bide oe ad eee sb Od debe e ee eae eee eee ole be 119
Andreas Haeberlen, University of Pennsylvania; Paarijaat Aditya, Rodrigo Rodrigues, and Peter Druschel,
Max Planck Institute for Software Systems (MPI-SWS)
Concurrency Bugs
Bypassing Races in Live Applications with Execution Filters 0.2.00... 0... cece cnet teens 135
Jingyue Wu, Heming Cui, and Junfeng Yang, Columbia University
Effective Data-Race Detection for the Kernel . oo... 0.65553 eess see be bd nb eee eens bee ne eee he eee nE EEE: 151
John Erickson, Madanlal Musuvathi, Sebastian Burckhardt, and Kirk Olynyk, Microsoft Research
Ad Hoc Synchronization Considered Harmful ........ 00... eee cette nent e nee nes 163
Weiwei Xiong, University of Illinois at Urbana-Champaign; Soyeon Park, Jiaqi Zhang, and Yuanyuan Zhou,
University of California, San Diego; Zhiqiang Ma, Intel
USENIX Association 9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) ill
Tuesday, October 5
Deterministic Parallelism
Deterministic Process Groups i dOS «..:04:6 ececcs cietscee Giga aie ee Reeeeok GMa Rae wR ee ew ea de ee de 177
Tom Bergan, Nicholas Hunt, Luis Ceze, and Steven D. Gribble, University of Washington
Efficient System-Enforced Deterministic Parallelism. ... 00.0.0... cece ccc ete e nett e eens 193
Amittai Aviram, Shu-Chun Weng, Sen Hu, and Bryan Ford, Yale University
Stable Deterministic Multithreading through Schedule Memoization ............. 0.0 e cee eee eee eee ee 207
Heming Cui, Jingyue Wu, Chia-che Tsai,and Junfeng Yang, Columbia University
Systems Management
Enabling Configuration-Independent Automation by Non-Expert Users. ......... 00.00 cece eee eee ene 223
Nate Kushman and Dina Katabi, Massachusetts Institute of Technology
Automating Configuration Troubleshooting with Dynamic Information Flow Analysis................-0005 237
Mona Attariyan and Jason Flinn, University of Michigan
Inside the Data Center, 2
Large-scale Incremental Processing Using Distributed Transactions and Notifications ....................- 251
Daniel Peng and Frank Dabek, Google, Inc.
Reining in the Outliers in Map-Reduce Clusters using Mantri... 2... 6... eee eet eens 265
Ganesh Ananthanarayanan, Microsoft Research and UC Berkeley; Srikanth Kandula and Albert Greenberg,
Microsoft Research; Ion Stoica, UC Berkeley; Yi Lu, Microsoft Research; Bikas Saha and Edward Harris,
Microsoft Bing
Transactional Consistency and Automatic Management in an Application Data Cache ..................05. 279
Dan R.K. Ports, Austin T. Clements, Irene Zhang, Samuel Madden, and Barbara Liskov, MIT CSAIL
Piccolo: Building Fast, Distributed Programs with Partitioned Tables........... 0... ccc eee eee een eee 293
Russell Power and Jinyang Li, New York University
Cloud Storage
Depot: Cloud Storage with Minimal Trust... 2.0.0.0... ccc ne ne t een nen ene nee 307
Prince Mahajan, Srinath Setty, Sangmin Lee, Allen Clement, Lorenzo Alvisi, Mike Dahlin, and Michael Walfish,
The University of Texas at Austin
Comet: An Active Distributed Key-Value Store: 240s ics des ca ebb bees A eee OEE EEG dR Se Seen 323
Roxana Geambasu, Amit A. Levy, Tadayoshi Kohno, Arvind Krishnamurthy, and Henry M. Levy, University of
Washington
SPORC: Group Collaboration using Untrusted Cloud Resources. ......... 0... c eee eee eens 337
Ariel J. Feldman, William P. Zeller, Michael J. Freedman, and Edward W. Felten, Princeton University
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) USENIX Association
Wednesday, October 6
Production Networks
Onix: A Distributed Control Platform for Large-scale Production Networks. ............ 0:0 e eee ee eee eens 351
Teemu Koponen, Martin Casado, Natasha Gude, and Jeremy Stribling, Nicira Networks; Leon Poutievski, Min
Zhu, and Rajiv Ramanathan, Google; Yuichiro Iwata, Hiroaki Inoue, and Takayuki Hama, NEC; Scott Shenker,
International Computer Science Institute (ICSI) and UC Berkeley
Can the Production Network Be the Testbed? .. 0.0... eect ne tne n een eens 365
Rob Sherwood, Deutsche Telekom Inc. R&D Lab; Glen Gibb and Kok-Kiong Yap,Stanford University; Guido
Appenzeller, Big Switch Networks; Martin Casado, Nicira Networks; Nick McKeown and Guru Parulkar,
Stanford University
Building Extensible Networks with Rule-Based Forwarding .......... 00... e cece e eect teens 379
Lucian Popa, University of California, Berkeley, and ICSI, Berkeley; Norbert Egi, Lancaster University; Sylvia
Ratnasamy, Intel Labs, Berkeley; Ion Stoica, University of California, Berkeley
Mobility
TaintDroid: An Information-Flow Tracking System for Realtime Privacy Monitoring on Smartphones ........ 393
William Enck, The Pennsylvania State University; Peter Gilbert, Duke University; Byung-gon Chun, Intel
Labs; Landon P. Cox, Duke University; Jaeyeon Jung, Intel Labs; Patrick McDaniel, The Pennsylvania State
University; Anmol N. Sheth, Intel Labs
StarTrack Next Generation: A Scalable Infrastructure for Track-Based Applications. ................00000- 409
Maya Haridasan, Iqbal Mohomed, Doug Terry, Chandramohan A. Thekkath, and Li Zhang, Microsoft Research
Silicon Valley
Virtualization
The Turtles Project: Design and Implementation of Nested Virtualization ......... 00.0... cece eee eee 423
Muli Ben-Yehuda, IBM Research—Haifa; Michael D. Day, IBM Linux Technology Center; Zvi Dubitzky,
Michael Factor, Nadav Har’El, and Abel Gordon, IBM Research—Haifa; Anthony Liguori, IBM Linux
Technology Center; Orit Wasserman and Ben-Ami Yassour, IBM Research—Haifa
mClock: Handling Throughput Variability for Hypervisor IO Scheduling. .............. 00.0.0 e cece eee 437
Ajay Gulati, VMware Inc.; Arif Merchant, HP Labs; Peter J. Varman, Rice University
Virtualize Everything but Time s...2 s20.400%ice howe beaeGirededieetieetand eed bee ed aea des Gio d 451
Timothy Broomhead, Laurence Cremean, Julien Ridoux, and Darryl Veitch, Center for Ultra-Broadband
Information Networks (CUBIN), The University of Melbourne
USENIX Association 9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) v
Message from the Program Co-Chairs
Welcome to OSDI ’10, the biggest OSDI yet, with 32 papers selected from an all-time high of 199 submissions. In
approaching the task of chairing OSDI, we started with the explicit intention of accepting a larger set of papers,
consistent with the growth in the field. Below we outline some of the rationale behind this goal, and the process we
applied to achieve it.
Computer systems research is growing as a community. We believe that progress on computer systems research is
limited by manpower, not by the limits of a finite domain for interesting research. By implication, as the number
of systems researchers increases, the volume of interesting research likely goes up as well. Year after year, top
research programs add faculty or research positions in the systems area, while at the same time new programs es-
tablish their presence in the field, including newfound growth outside the traditionally strong geographies. The ex-
pansion of our community is consistent with the robust scientific and commercial application of computer systems
research, providing a strong economic basis for this growth. We believe a larger OSDI program is an appropriate
reflection of this growth in the systems community.
We were also motivated by the challenge in making meaningful distinctions, under the pressure of program com-
mittee deadlines, between papers that are almost accepted and those almost rejected. The fragility of PC decision
process has been documented and discussed elsewhere [A08]. Too often, rejections seem arbitrary in retrospect,
hinging on the nuances of a PC discussion rather than clear merit. In accepting more papers we hope to incremen-
tally improve on the fragility of these decisions, while also building a program that is more diverse and therefore of
broader interest.
This goal of a larger program was a consideration throughout the review process. The PC was split into two groups:
a “heavy” PC who participated in the first two rounds of reviewing, and a “heavier” PC who also reviewed papers
in round three and attended a face-to-face meeting to decide final outcomes. In the first round, each paper received
two reviews and approximately 35 papers were pruned. To reduce the risk of a premature pruning decision, we
allowed reviewers to “rescue” a pruned paper by simply stating their support, with no discussion required. Each
round-2 paper received three additional reviews. Another 80 or so papers were pruned after this round. This left us
with a pool of 85 papers, each of which received two or three additional reviews in preparation for the PC meeting.
After the second and third review rounds, borderline papers were discussed electronically by the reviewers and
rejected by consensus of the reviewers.
In the single-day, face-to-face PC meeting each remaining paper was presented by a reviewer, generally an advo-
cate, followed by a time-limited discussion. Based on the first discussion, we binned each paper into one of four
categories: “accept,” “acceptable,” “questionable,” and “reject.” No rejects were allowed in the first part of the day,
the goal of this rule being to avoid the problem of a negative start leading to rejecting good papers early. When all
papers had been discussed once, we briefly considered and then accepted the “acceptable” papers as a group, then
began the difficult work of reconsidering the “questionable” papers. At the end of the meeting about 30 papers had
been accepted.
29 66
In the days following the PC meeting, a small set of additional papers were accepted based on an email vote by the
heavier PC members. While unusual, we justified this process based on our goal to create a larger and more inter-
esting program, and a sentiment shared by many PC members that the PC discussion had not given due consider-
ation to several of the best liked but most controversial papers. In retrospect we believe these late accepts allowed
us to create a stronger and more interesting program, and we would encourage future PC chairs to plan an appro-
priate process for thoughtful consideration of difficult papers after the bustle of the PC meeting has subsided. For
example, even with a single-day PC meeting, it might make sense to put a small set of papers into an “overnight”
category, allowing a broader collection of PC members to study them before a final decision the next day.
Apart from the review process, we took some additional measures to try and get more reviews and reviewers in a
mindset to accept. We encouraged positivity, following Hill and McKinley’s excellent advice [HM05]. We strictly
applied conflict-of-interest rules, such that conflicted PC members were not given access to results for conflicted
USENIX Association 9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) vil
papers until notifications had been sent to authors. We tried to lighten the PC load from papers that had no chance
of acceptance, to leave more quality time for the remaining papers.
Before we close we’d like to briefly acknowledge a few individuals who made a difference in our bringing this
program to you. The USENIX staff was fantastic throughout the entire process. We also thank Eddie Kohler for his
continued support of HotCRP, a truly wonderful piece of software. We also would like to acknowledge the program
committee for their tireless efforts and thoughtful reviews, and Haryadi Gunawi for his detailed note-taking during
the PC meeting. Finally, we would like to thank our families and the families of PC members for supporting (and
tolerating!) the long hours required to do this kind of work.
Thank you for attending OSDI ’10, and have a great conference!
Remzi Arpaci-Dusseau, University of Wisconsin, Madison
Brad Chen, Google
OSDI ’10 Program Co-Chairs
REFERENCES
[A08] “Towards a Model of Computer Systems Research,” Thomas Anderson, University of Washington. WOWCS
°08, April 2008.
[HMO05] “Notes on Constructive and Positive Reviewing,” Mark Hill, University of Wisconsin—Madison, and
Kathryn S McKinley, University of Texas at Austin: http://userweb.cs.utexas.edu/users/mckinley/notes/reviewing
-html, May 2005.
Vili 9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) USENIX Association
USENIX Association
An Analysis of Linux Scalability to Many Cores
Silas Boyd-Wickizer, Austin T. Clements, Yandong Mao, Aleksey Pesterev,
M. Frans Kaashoek, Robert Morris, and Nickolai Zeldovich
MIT CSAIL
ABSTRACT
This paper analyzes the scalability of seven system appli-
cations (Exim, memcached, Apache, PostgreSQL, gmake,
Psearchy, and MapReduce) running on Linux on a 48-
core computer. Except for gmake, all applications trigger
scalability bottlenecks inside a recent Linux kernel. Us-
ing mostly standard parallel programming techniques—
this paper introduces one new technique, sloppy coun-
ters—these bottlenecks can be removed from the kernel
or avoided by changing the applications slightly. Modify-
ing the kernel required in total 3002 lines of code changes.
A speculative conclusion from this analysis is that there
is no scalability reason to give up on traditional operating
system organizations just yet.
1 INTRODUCTION
There is a sense in the community that traditional kernel
designs won’t scale well on multicore processors: that
applications will spend an increasing fraction of their time
in the kernel as the number of cores increases. Promi-
nent researchers have advocated rethinking operating sys-
tems [10, 28, 43] and new kernel designs intended to al-
low scalability have been proposed (e.g., Barrelfish [11],
Corey [15], and fos [53]). This paper asks whether tradi-
tional kernel designs can be used and implemented in a
way that allows applications to scale.
This question is difficult to answer conclusively, but
we attempt to shed a small amount of light on it. We
analyze scaling a number of system applications on
Linux running with a 48-core machine. We examine
Linux because it has a traditional kernel design, and be-
cause the Linux community has made great progress in
making it scalable. The applications include the Exim
mail server [2], memcached [3], Apache serving static
files [1], PostgreSQL [4], gmake [23], the Psearchy file
indexer [35, 48], and a multicore MapReduce library [38].
These applications, which we will refer to collectively
as MOSBENCH, are designed for parallel execution and
stress many major Linux kernel components.
Our method for deciding whether the Linux kernel
design is compatible with application scalability is as
follows. First we measure scalability of the MOSBENCH
applications on a recent Linux kernel (2.6.35-rc5, released
July 12, 2010) with 48 cores, using the in-memory tmpfs
file system to avoid disk bottlenecks. gmake scales well,
but the other applications scale poorly, performing much
less work per core with 48 cores than with one core. We
attempt to understand and fix the scalability problems, by
modifying either the applications or the Linux kernel. We
then iterate, since fixing one scalability problem usually
exposes further ones. The end result for each applica-
tion is either good scalability on 48 cores, or attribution
of non-scalability to a hard-to-fix problem with the ap-
plication, the Linux kernel, or the underlying hardware.
The analysis of whether the kernel design is compatible
with scaling rests on the extent to which our changes to
the Linux kernel turn out to be modest, and the extent
to which hard-to-fix problems with the Linux kernel ulti-
mately limit application scalability.
As part of the analysis, we fixed three broad kinds of
scalability problems for MOSBENCH applications: prob-
lems caused by the Linux kernel implementation, prob-
lems caused by the applications’ user-level design, and
problems caused by the way the applications use Linux
kernel services. Once we identified a bottleneck, it typi-
cally required little work to remove or avoid it. In some
cases we modified the application to be more parallel, or
to use kernel services in a more scalable fashion, and in
others we modified the kernel. The kernel changes are all
localized, and typically involve avoiding locks and atomic
instructions by organizing data structures in a distributed
fashion to avoid unnecessary sharing. One reason the
required changes are modest is that stock Linux already
incorporates many modifications to improve scalability.
More speculatively, perhaps it is the case that Linux’s
system-call API is well suited to an implementation that
avoids unnecessary contention over kernel objects.
The main contributions of this paper are as follows.
The first contribution is a set of 16 scalability improve-
ments to the Linux 2.6.35-rc5 kernel, resulting in what we
refer to as the patched kernel, PK. A few of the changes
rely on a new idea, which we call sloppy counters, that
has the nice property that it can be used to augment shared
counters to make some uses more scalable without having
to change all uses of the shared counter. This technique
is particularly effective in Linux because typically only
a few uses of a given shared counter are scalability bot-
tlenecks; sloppy counters allow us to replace just those
few uses without modifying the many other uses in the
kernel. The second contribution is a set of application
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) 1
benchmarks, MOSBENCH, to measure scalability of op-
erating systems, which we make publicly available. The
third is a description of the techniques required to im-
prove the scalability of the MOSBENCH applications. Our
final contribution is an analysis using MOSBENCH that
suggests that there is no immediate scalability reason to
give up on traditional kernel designs.
The rest of the paper is organized as follows. Section 2
relates this paper to previous work. Section 3 describes
the applications in MOSBENCH and what operating sys-
tem components they stress. Section 4 summarizes the
differences between the stock and PK kernels. Section 5
reports on the scalability of MOSBENCH on the stock
Linux 2.6.35-rc5 kernel and the PK kernel. Section 6
discusses the implications of the results. Section 7 sum-
marizes this paper’s conclusions.
2 RELATED WORK
There is a long history of work in academia and industry
to scale Unix-like operating systems on shared-memory
multiprocessors. Research projects such as the Stanford
FLASH [33] as well as companies such as IBM, Se-
quent, SGI, and Sun have produced shared-memory ma-
chines with tens to hundreds processors running variants
of Unix. Many techniques have been invented to scale
software for these machines, including scalable locking
(e.g., [41]), wait-free synchronization (e.g., [27]), mul-
tiprocessor schedulers (e.g., [8, 13, 30, 50]), memory
management (e.g., [14, 19, 34, 52, 57]), and fast message
passing using shared memory (e.g., [12, 47]). Textbooks
have been written about adapting Unix for multiproces-
sors (e.g., [46]). These techniques have been incorporated
in current operating systems such as Linux, Mac OS X,
Solaris, and Windows. Cantrill and Bonwick summarize
the historical context and real-world experience [17].
This paper extends previous scalability studies by ex-
amining a large set of systems applications, by using a
48-core PC platform, and by detailing a particular set of
problems and solutions in the context of Linux. These
solutions follow the standard parallel programming tech-
nique of factoring data structures so that each core can
operate on separate data when sharing is not required, but
such that cores can share data when necessary.
Linux scalability improvements. Early multiproces-
sor Linux kernels scaled poorly with kernel-intensive par-
allel workloads because the kernel used coarse-granularity
locks for simplicity. Since then the Linux commu-
nity has redesigned many kernel subsystems to im-
prove scalability (e.g., Read-Copy-Update (RCV) [39],
local run queues [6], libnuma [31], and improved
load-balancing support [37]). The Linux symposium
(www. linuxsymposium.org) features papers related to
scalability almost every year. Some of the redesigns are
based on the above-mentioned research, and some com-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
panies, such as IBM and SGI [16], have contributed code
directly. Kleen provides a brief history of Linux kernel
modifications for scaling and reports some areas of poor
scalability in a recent Linux version (2.6.31) [32]. In this
paper, we identify additional kernel scaling problems and
describes how to address them.
Linux scalability studies. Gough et al. study the scal-
ability of Oracle Database 10g running on Linux 2.6.18
on dual-core Intel Itanium processors [24]. The study
finds problems with the Linux run queue, slab alloca-
tor, and I/O processing. Cui et al. uses the TPCC-UVa
and Sysbench-OLTP benchmarks with PostgreSQL to
study the scalability of Linux 2.6.25 on an Intel 8-core
system [56], and finds application-internal bottlenecks
as well as poor kernel scalability in System V IPC. We
find that these problems have either been recently fixed
by the Linux community or are a consequence of fixable
problems in PostgreSQL.
Veal and Foong evaluate the scalability of Apache run-
ning on Linux 2.6.20.3 on an 8-core AMD Opteron com-
puter using SPECweb2005 [51]. They identify Linux scal-
ing problems in the kernel implementations of scheduling
and directory lookup, respectively. On a 48-core com-
puter, we also observe directory lookup as a scalability
problem and PK applies a number of techniques to ad-
dress this bottleneck. Pesterev ef al. identify scalability
problems in the Linux 2.6.30 network code using mem-
cached and Apache [44]. The PK kernel addresses these
problems by using a modern network card that supports a
large number of virtual queues (similar to the approach
taken by Route Bricks [21]).
Cui et al. describe microbenchmarks for measuring
multicore scalability and report results from running them
on Linux on a 32-core machine [55]. They find a number
of scalability problems in Linux (e.g., memory-mapped
file creation and deletion). Memory-mapped files show
up as a scalability problem in one MOSBENCH application
when multiple threads run in the same address space with
memory-mapped files.
A number of new research operating systems use scal-
ability problems in Linux as motivation. The Corey pa-
per [15] identified bottlenecks in the Linux file descriptor
and virtual memory management code caused by unneces-
sary sharing. Both of these bottlenecks are also triggered
by MOSBENCH applications. The Barrelfish paper [11]
observed that Linux TLB shootdown scales poorly. This
problem is not observed in the MOSBENCH applications.
Using microbenchmarks, the fos paper [53] finds that the
physical page allocator in Linux 2.6.24.7 does not scale
beyond 8 cores and that executing the kernel and applica-
tions on the same core results in cache interference and
high miss rates. We find that the page allocator isn’t a
bottleneck for MOSBENCH applications on 48 cores (even
though they stress memory allocation), though we have
USENIX Association
USENIX Association
reason to believe it would be a problem with more cores.
However, the problem appears to be avoidable by, for
example, using super-pages or modifying the kernel to
batch page allocation.
Solaris scalability studies. Solaris provides a UNIX
API and runs on SPARC-based and x86-based multi-
core processors. Solaris incorporates SNZIs [22], which
are similar to sloppy counters (see section 4.3). Tseng
et al. report that SAP-SD, IBM Trade and several syn-
thetic benchmarks scale well on an 8-core SPARC system
running Solaris 10 [49]. Zou et al. encountered coarse
grained locks in the UDP networking stack of Solaris
10 that limited scalability of the OpenSER SIP proxy
server on an 8-core SPARC system [29]. Using the mi-
crobenchmarks mentioned above [55], Cui et al. compare
FreeBSD, Linux, and Solaris [54], and find that Linux
scales better on some microbenchmarks and Solaris scales
better on others. We ran some of the MOSBENCH appli-
cations on Solaris 10 on the 48-core machine used for
this paper. While the Solaris license prohibits us from re-
porting quantitative results, we observed similar or worse
scaling behavior compared to Linux; however, we don’t
know the causes or whether Solaris would perform better
on SPARC hardware. We hope, however, that this paper
helps others who might analyze Solaris.
3 THE MOSBENCH APPLICATIONS
To stress the kernel we chose two sets of applications:
1) applications that previous work has shown not to
scale well on Linux (memcached; Apache; and Metis, a
MapReduce library); and 2) applications that are designed
for parallel execution and are kernel intensive (gmake,
PostgreSQL, Exim, and Psearchy). Because many ap-
plications are bottlenecked by disk writes, we used an
in-memory tmpfs file system to explore non-disk limita-
tions. We drive some of the applications with synthetic
user workloads designed to cause them to use the ker-
nel intensively, with realism a secondary consideration.
This collection of applications stresses important parts
of many kernel components (e.g., the network stack, file
name cache, page cache, memory manager, process man-
ager, and scheduler). Most spend a significant fraction
of their CPU time in the kernel when run on a single
core. All but one encountered serious scaling problems
at 48 cores caused by the stock Linux kernel. The rest of
this section describes the selected applications, how they
are parallelized, and what kernel services they stress.
3.1 Mail server
Exim [2] is a mail server. We operate it in a mode where
a single master process listens for incoming SMTP con-
nections via TCP and forks a new process for each con-
nection, which in turn accepts the incoming mail, queues
it in a shared set of spool directories, appends it to the
per-user mail file, deletes the spooled mail, and records
the delivery in a shared log file. Each per-connection pro-
cess also forks twice to deliver each message. With many
concurrent client connections, Exim has a good deal of
parallelism. It spends 69% of its time in the kernel on
a single core, stressing process creation and small file
creation and deletion.
3.2 Object cache
memcached [3] is an in-memory key-value store often
used to improve web application performance. A single
memcached server running on multiple cores is bottle-
necked by an internal lock that protects the key-value hash
table. To avoid this problem, we run multiple memcached
servers, each on its own port, and have clients determin-
istically distribute key lookups among the servers. This
organization allows the servers to process requests in par-
allel. When request sizes are small, memcached mainly
stresses the network stack, spending 80% of its time pro-
cessing packets in the kernel at one core.
3.3. Web server
Apache [1] is a popular Web server, which previous work
(e.g., [51]) has used to study Linux scalability. We run a
single instance of Apache listening on port 80. We config-
ure this instance to run one process per core. Each process
has a thread pool to service connections; one thread is
dedicated to accepting incoming connections while the
other threads process the connections. In addition to the
network stack, this configuration stresses the file system
(in particular directory name lookup) because it stats and
opens a file on every request. Running on a single core,
an Apache process spends 60% of its execution time in
the kernel.
3.4 Database
PostgreSQL [4] is a popular open source SQL database,
which, unlike many of our other workloads, makes exten-
sive internal use of shared data structures and synchro-
nization. PostgreSQL also stresses many shared resources
in the kernel: it stores database tables as regular files
accessed concurrently by all PostgreSQL processes, it
starts one process per connection, it makes use of kernel
locking interfaces to synchronize and load balance these
processes, and it communicates with clients over TCP
sockets that share the network interface.
Ideally, PostgreSQL would scale well for read-mostly
workloads, despite its inherent synchronization needs.
PostgreSQL relies on snapshot isolation, a form of opti-
mistic concurrency control that avoids most read locks.
Furthermore, most write operations acquire only row-
level locks exclusively and acquire all coarser-grained
locks in shared modes. Thus, in principle, PostgreSQL
should exhibit little contention for read-mostly workloads.
In practice, PostgreSQL is limited by bottlenecks in both
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) 3
its own code and in the kernel. For a read-only work-
load that avoids most application bottlenecks, PostgreSQL
spends only 1.5% of its time in the kernel with one core,
but this grows to 82% with 48 cores.
3.5 Parallel build
gmake [23] is an implementation of the standard make
utility that supports executing independent build rules
concurrently. gmake is the unofficial default benchmark
in the Linux community since all developers use it to
build the Linux kernel. Indeed, many Linux patches
include comments like “This speeds up compiling the
kernel.” We benchmarked gmake by building the stock
Linux 2.6.35-re5 kernel with the default configuration
for x86_64. gmake creates more processes than there are
cores, and reads and writes many files. The execution
time of gmake is dominated by the compiler it runs, but
system time is not negligible: with one core, 7.6% of the
execution time is system time.
3.6 File indexer
Psearchy is a parallel version of searchy [35, 48], a pro-
gram to index and query Web pages. We focus on the
indexing component of searchy because it is more system
intensive. Our parallel version, pedsort, runs the searchy
indexer on each core, sharing a work queue of input files.
Each core operates in two phases. In phase 1, it pulls input
files off the work queue, reading each file and recording
the positions of each word in a per-core hash table. When
the hash table reaches a fixed size limit, it sorts it alpha-
betically, flushes it to an intermediate index on disk, and
continues processing input files. Phase 1 is both compute
intensive (looking up words in the hash table and sorting
it) and file-system intensive (reading input files and flush-
ing the hash table). To avoid stragglers in phase 1, the
initial work queue is sorted so large files are processed
first. Once the work queue is empty, each core merges
the intermediate index files it produced, concatenating the
position lists of words that appear in multiple intermedi-
ate indexes, and generates a binary file that records the
positions of each word and a sequence of Berkeley DB
files that map each word to its byte offset in the binary
file. To simplify the scalability analysis, each core starts
a new Berkeley DB every 200,000 entries, eliminating
a logarithmic factor and making the aggregate work per-
formed by the indexer constant regardless of the number
of cores. Unlike phase 1, phase 2 is mostly file-system
intensive. While pedsort spends only 1.9% of its time
in the kernel at one core, this grows to 23% at 48 cores,
indicating scalability limitations.
3.7 MapReduce
Metis is a MapReduce [20] library for single multicore
servers inspired by Phoenix [45]. We use Metis with an
application that generates inverted indices. This workload
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
allocates large amounts of memory to hold temporary
tables, stressing the kernel memory allocator and soft page
fault code. This workload spends 3% of its runtime in the
kernel with one core, but this rises to 16% at 48 cores.
4 KERNEL OPTIMIZATIONS
The MOSBENCH applications trigger a few scalability
bottlenecks in the kernel. We describe the bottlenecks
and our solutions here, before presenting detailed per-
application scaling results in Section 5, because many
of the bottlenecks are common to multiple applications.
Figure | summarizes the bottlenecks. Some of these prob-
lems have been discussed on the Linux kernel mailing
list and solutions proposed; perhaps the reason these solu-
tions have not been implemented in the standard kernel is
that the problems are not acute on small-scale SMPs or
are masked by I/O delays in many applications. Figure |
also summarizes our solution for each bottleneck.
4.1 Scalability tutorial
Why might one expect performance to scale well with the
number of cores? If a workload consists of an unlimited
supply of tasks that do not interact, then you’d expect to
get linear increases in total throughput by adding cores
and running tasks in parallel. In real life parallel tasks
usually interact, and interaction usually forces serial ex-
ecution. Amdahl’s Law summarizes the result: however
small the serial portion, it will eventually prevent added
cores from increasing performance. For example, if 25%
of a program is serial (perhaps inside some global locks),
then any number of cores can provide no more than 4-
times speedup.
Here are a few types of serializing interactions that
the MOSBENCH applications encountered. These are all
classic considerations in parallel programming, and are
discussed in previous work such as [17].
The tasks may lock a shared data structure, so that
increasing the number of cores increases the lock
wait time.
The tasks may write a shared memory location, so
that increasing the number of cores increases the
time spent waiting for the cache coherence proto-
col to fetch the cache line in exclusive mode. This
problem can occur even in lock-free shared data
structures.
The tasks may compete for space in a limited-size
shared hardware cache, so that increasing the number
of cores increases the cache miss rate. This problem
can occur even if tasks never share memory.
The tasks may compete for other shared hardware
resources such as inter-core interconnect or DRAM
USENIX Association
USENIX Association
Parallel accept
Apache
Concurrent accept system calls contend on shared socket fields. = User per-core backlog queues for listening sockets.
dentry reference counting Apache, Exim
File name resolution contends on directory entry reference counts. | => Use sloppy counters to reference count directory entry objects.
Mount point (vfsmount) reference counting Apache, Exim
Walking file name paths contends on mount point reference counts. => Use sloppy counters for mount point objects.
IP packet destination (dst_entry) reference counting memcached, Apache
IP packet transmission contends on routing table entries. => Use sloppy counters for IP routing table entries.
Protocol memory usage tracking memcached, Apache
Cores contend on counters for tracking protocol memory consumption. = Use sloppy counters for protocol usage counting.
Acquiring directory entry (dentry) spin locks Apache, Exim
Walking file name paths contends on per-directory entry spin locks. => Use a lock-free protocol in dlookup for checking filename matches.
Mount point table spin lock Apache, Exim
Resolving path names to mount points contends on a global spin lock. = Use per-core mount table caches.
Adding files to the open list Apache, Exim
Cores contend on a per-super block list that tracks open files. => Use per-core open file lists for each super block that has open files.
Allocating DMA buffers memcached, Apache
DMA memory allocations contend on the memory node 0 spin lock. = Allocate Ethernet device DMA buffers from the local memory node.
False sharing in net_device and device
memcached, Apache, PostgreSQL
False sharing causes contention for read-only structure fields.
False sharing in page
Place read-only fields on their own cache lines.
Exim
False sharing causes contention for read-mostly structure fields.
inode lists
Place read-only fields on their own cache lines.
memcached, Apache
Cores contend on global locks protecting lists used to track inodes.
Deache lists
=>
Avoid acquiring the locks when not necessary.
memcached, Apache
Cores contend on global locks protecting lists used to track dentrys.
Per-inode mutex
=>
Avoid acquiring the locks when not necessary.
PostgreSQL
Cores contend on a per-inode mutex in 1seek. => Use atomic reads to eliminate the need to acquire the mutex.
Super-page fine grained locking Metis
Super-page soft page faults contend on a per-process mutex. => Protect each super-page memory mapping with its own mutex.
Zeroing super-pages Metis
Zeroing super-pages flushes the contents of on-chip caches.
=>
Use non-caching instructions to zero the contents of super-pages.
Figure 1: A summary of Linux scalability problems encountered by MOSBENCH applications and their corresponding fixes. The fixes add 2617 lines
of code to Linux and remove 385 lines of code from Linux.
interfaces, so that additional cores spend their time
waiting for those resources rather than computing.
e There may be too few tasks to keep all cores busy,
so that increasing the number of cores leads to more
idle cores.
Many scaling problems manifest themselves as delays
caused by cache misses when a core uses data that other
cores have written. This is the usual symptom both for
lock contention and for contention on lock-free mutable
data. The details depend on the hardware cache coherence
protocol, but the following is typical. Each core has a
data cache for its own use. When a core writes data that
other cores have cached, the cache coherence protocol
forces the write to wait while the protocol finds the cached
copies and invalidates them. When a core reads data
that another core has just written, the cache coherence
protocol doesn’t return the data until it finds the cache that
holds the modified data, annotates that cache to indicate
there is a copy of the data, and fetches the data to the
reading core. These operations take about the same time
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
as loading data from off-chip RAM (hundreds of cycles),
so sharing mutable data can have a disproportionate effect
on performance.
Exercising the cache coherence machinery by modify-
ing shared data can produce two kinds of scaling problems.
First, the cache coherence protocol serializes modifica-
tions to the same cache line, which can prevent parallel
speedup. Second, in extreme cases the protocol may
saturate the inter-core interconnect, again preventing addi-
tional cores from providing additional performance. Thus
good performance and scalability often demand that data
be structured so that each item of mutable data is used by
only one core.
In many cases scaling bottlenecks limit performance
to some maximum, regardless of the number of cores. In
other cases total throughput decreases as the number of
cores grows, because each waiting core slows down the
cores that are making progress. For example, non-scalable
spin locks produce per-acquire interconnect traffic that is
proportional to the number of waiting cores; this traffic
may slow down the core that holds the lock by an amount
proportional to the number of waiting cores [41]. Acquir-
5
ing a Linux spin lock takes a few cycles if the acquiring
core was the previous lock holder, takes a few hundred
cycles if another core last held the lock and there is no
contention, and are not scalable under contention.
Performance is often the enemy of scaling. One way
to achieve scalability is to use inefficient algorithms, so
that each core busily computes and makes little use of
shared resources such as locks. Conversely, increasing
the efficiency of software often makes it less scalable, by
increasing the fraction of time it uses shared resources.
This effect occurred many times in our investigations of
MOSBENCH application scalability.
Some scaling bottlenecks cannot easily be fixed, be-
cause the semantics of the shared resource require serial
access. However, it is often the case that the implementa-
tion can be changed so that cores do not have to wait for
each other. For example, in the stock Linux kernel the set
of runnable threads is partitioned into mostly-private per-
core scheduling queues; in the common case, each core
only reads, writes, and locks its own queue [36]. Many
scaling modifications to Linux follow this general pattern.
Many of our scaling modifications follow this same
pattern, avoiding both contention for locks and contention
for the underlying data. We solved other problems using
well-known techniques such as lock-free protocols or fine-
grained locking. In all cases we were able to eliminate
scaling bottlenecks with only local changes to the kernel
code. The following subsections explain our techniques.
4.2 Multicore packet processing
The Linux network stack connects different stages of
packet processing with queues. A received packet typ-
ically passes through multiple queues before finally ar-
riving at a per-socket queue, from which the application
reads it with a system call like read or accept. Good
performance with many cores and many independent net-
work connections demands that each packet, queue, and
connection be handled by just one core [21, 42]. This
avoids inter-core cache misses and queue locking costs.
Recent Linux kernels take advantage of network cards
with multiple hardware queues, such as Intel’s 82599
10Gbit Ethernet (IXGBE) card, or use software tech-
niques, such as Receive Packet Steering [26] and Receive
Flow Steering [25], to attempt to achieve this property.
With a multi-queue card, Linux can be configured to as-
sign each hardware queue to a different core. Transmit
scaling is then easy: Linux simply places outgoing pack-
ets on the hardware queue associated with the current
core. For incoming packets, such network cards provide
an interface to configure the hardware to enqueue incom-
ing packets matching a particular criteria (e.g., source IP
address and port number) on a specific queue and thus
to a particular core. This spreads packet processing load
across cores. However, the IXGBE driver goes further:
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
for each core, it samples every 20" outgoing TCP packet
and updates the hardware’s flow directing tables to de-
liver further incoming packets from that TCP connection
directly to the core.
This design typically performs well for long-lived con-
nections, but poorly for short ones. Because the technique
is based on sampling, it is likely that the majority of
packets on a given short connection will be misdirected,
causing cache misses as Linux delivers to the socket on
one core while the socket is used on another. Furthermore,
because few packets are received per short-lived connec-
tion, misdirecting even the initial handshake packet of a
connection imposes a significant cost.
For applications like Apache that simultaneously ac-
cept connections on all cores from the same listening
socket, we address this problem by allowing the hard-
ware to determine which core and thus which application
thread will handle an incoming connection. We modify
accept to prefer connections delivered to the local core’s
queue. Then, if the application processes the connection
on the same core that accepted it (as in Apache), all pro-
cessing for that connection will remain entirely on one
core. Our solution has the added benefit of addressing
contention on the lock that protects the single listening
socket’s connection backlog queue.
To implement this, we configured the IXGBE to direct
each packet to a queue (and thus core) using a hash of the
packet headers designed to deliver all of a connection’s
packets (including the TCP handshake packets) to the
same core. We then modified the code that handles TCP
connection setup requests to queue requests on a per-core
backlog queue for the listening socket, so that a thread
will accept and process connections that the IXGBE di-
rects to the core running that thread. If accept finds the
current core’s backlog queue empty, it attempts to steal
a connection request from a different core’s queue. This
arrangement provides high performance for short connec-
tions by processing each connection entirely on one core.
If threads were to move from core to core while handling
a single connection, a combination of this technique and
the current sampling approach might be best.
4.3 Sloppy counters
Linux uses shared counters for reference-counted garbage
collection and to manage various resources. These coun-
ters can become bottlenecks if many cores update them.
In these cases lock-free atomic increment and decrement
instructions do not help, because the coherence hardware
serializes the operations on a given counter.
The MOSBENCH applications encountered bottle-
necks from reference counts on directory entry objects
(dentrys), mounted file system objects (vfsmounts), net-
work routing table entries (dst_entrys), and counters
USENIX Association
USENIX Association
Core 0
oO
LO
So
0
OF
Core 1
a
a
a
Ts
as =
ak
cas
a
Or
Time
Figure 2: An example of the kernel using a sloppy counter for dentry
reference counting. A large circle represents a local counter, and a gray
dot represents a held reference. In this figure, a thread on core 0 first
acquires a reference from the central counter. When the thread releases
this reference, it adds the reference to the local counter. Finally, another
thread on core 0 is able to acquire the spare reference without touching
the central counter.
tracking the amount of memory allocated by each net-
work protocol (such as TCP or UDP).
Our solution, which we call sloppy counters, builds on
the intuition that each core can hold a few spare references
to an object, in hopes that it can give ownership of these
references to threads running on that core, without having
to modify the global reference count. More concretely,
a sloppy counter represents one logical counter as a sin-
gle shared central counter and a set of per-core counts
of spare references. When a core increments a sloppy
counter by V, it first tries to acquire a spare reference
by decrementing its per-core counter by V. If the per-
core counter is greater than or equal to V, meaning there
are sufficient local references, the decrement succeeds.
Otherwise the core must acquire the references from the
central counter, so it increments the shared counter by
V. When a core decrements a sloppy counter by V, it
releases these references as local spare references, incre-
menting its per-core counter by V. Figure 2 illustrates
incrementing and decrementing a sloppy counter. If the
local count grows above some threshold, spare references
are released by decrementing both the per-core count and
the central count.
Sloppy counters maintain the invariant that the sum
of per-core counters and the number of resources in use
equals the value in the shared counter. For example, a
shared dentry reference counter equals the sum of the
per-core counters and the number of references to the
dentry currently in use.
A core usually updates a sloppy counter by modifying
its per-core counter, an operation which typically only
needs to touch data in the core’s local cache (no waiting
for locks or cache-coherence serialization).
We added sloppy counters to count references to
dentrys, vfsmounts, and dst_entrys, and used sloppy
counters to track the amount of memory allocated by
each network protocol (such as TCP and UDP). Only
uses of a counter that cause contention need to be mod-
ified, since sloppy counters are backwards-compatible
with existing shared-counter code. The kernel code that
creates a sloppy counter allocates the per-core counters.
It is occasionally necessary to reconcile the central and
per-core counters, for example when deciding whether an
object can be de-allocated. This operation is expensive,
so sloppy counters should only be used for objects that
are relatively infrequently de-allocated.
Sloppy counters are similar to Scalable NonZero Indi-
cators (SNZD) [22], distributed counters [9], and approxi-
mate counters [5]. All of these techniques speed up incre-
ment/decrement by use of per-core counters, and require
significantly more work to find the true total value. Sloppy
counters are attractive when one wishes to improve the
performance of some uses of an existing counter without
having to modify all points in the code where the counter
is used. A limitation of sloppy counters is that they use
space proportional to the number of cores.
4.4 Lock-free comparison
We found situations in which MOSBENCH applications
were bottlenecked by low scalability for name lookups
in the directory entry cache. The directory entry cache
speeds up lookups by mapping a directory and a file name
to a dentry identifying the target file’s inode. When
a potential dentry is located, the lookup code acquires
a per-dentry spin lock to atomically compare several
fields of the dentry with the arguments of the lookup
function. Even though the directory cache has been op-
timized using RCU for scalability [40], the dentry spin
lock for common parent directories, such as /usr, was
sometimes a bottleneck even if the path names ultimately
referred to different files.
We optimized dentry comparisons using a lock-free
protocol similar to Linux’ lock-free page cache lookup
protocol [18]. The lock-free protocol uses a generation
counter, which the PK kernel increments after every mod-
ification to a directory entry (e.g.,mv foo bar). During
a modification (when the dentry spin lock is held), PK
temporarily sets the generation counter to 0. The PK ker-
nel compares dentry fields to the arguments using the
following procedure for atomicity:
e If the generation counter is 0, fall back to the lock-
ing protocol. Otherwise remember the value of the
generation counter.
e Copy the fields of the dentry to local variables. If
the generation afterwards differs from the remem-
bered value, fall back to the locking protocol.
e Compare the copied fields to the arguments. If there
is a match, increment the reference count unless it is
0, and return the dentry. If the reference count is 0,
fall back to the locking protocol.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) 7
The lock-free protocol improves scalability because it
allows cores to perform lookups for the same directory
entries without serializing.
4.5 Per-core data structures
We encountered three kernel data structures that caused
scaling bottlenecks due to lock contention: a per-super-
block list of open files that determines whether a read-
write file system can be remounted read-only, a table of
mount points used during path lookup, and the pool of
free packet buffers. Though each of these bottlenecks is
caused by lock contention, bottlenecks would remain if
we replaced the locks with finer grained locks or a lock
free protocol, because multiple cores update the data struc-
tures. Therefore our solutions refactor the data structures
so that in the common case each core uses different data.
We split the per-super-block list of open files into per-
core lists. When a process opens a file the kernel locks
the current core’s list and adds the file. In most cases
a process closes the file on the same core it opened it
on. However, the process might have migrated to another
core, in which case the file must be expensively removed
from the list of the original core. When the kernel checks
if a file system can be remounted read-only it must lock
and scan all cores’ lists.
We also added per-core vfsmount tables, each acting
as a cache for a central vfsmount table. When the kernel
needs to look up the vfsmount for a path, it first looks in
the current core’s table, then the central table. If the latter
succeeds, the result is added to the per-core table.
Finally, the default Linux policy for machines with
NUMA memory is to allocate packet buffers (skbuffs)
from a single free list in the memory system closest to the
I/O bus. This caused contention for the lock protecting
the free list. We solved this using per-core free lists.
4.6 Eliminating false sharing
We found some MOSBENCH applications caused false
sharing in the kernel. In the cases we identified, the ker-
nel located a variable it updated often on the same cache
line as a variable it read often. The result was that cores
contended for the falsely shared line, limiting scalabil-
ity. Exim per-core performance degraded because of false
sharing of physical page reference counts and flags, which
the kernel located on the same cache line of a page vari-
able. memcached, Apache, and PostgreSQL faced simi-
lar false sharing problems with net_device and device
variables. In all cases, placing the heavily modified data
on a separate cache line improved scalability.
4.7 Avoiding unnecessary locking
For small numbers of cores, lock contention in Linux
does not limit scalability for MOSBENCH applications.
With more than 16 cores, the scalability of memcached,
Apache, PostgreSQL, and Metis are limited by waiting for
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
Stock =—
K==_
So
oo
T
l
S
a
T
I
S
BR
T
I
S
iv
T
!
Per-core throughput at 48 cores relative to 1 core
Exim memcached Apache PostgreSQL gmake pedsort Metis
Figure 3: MOSBENCH results summary. Each bar shows the ratio of
per-core throughput with 48 cores to throughput on one core, with 1.0
indicating perfect scalability. Each pair of bars corresponds to one
application before and after our kernel and application modifications.
and acquiring spin locks and mutexes! in the file system
and virtual memory management code. In many cases we
were able to eliminate acquisitions of the locks altogether
by modifying the code to detect special cases when ac-
quiring the locks was unnecessary. In one case, we split
a mutex protecting all the super page mappings into one
mutex per mapping.
5 EVALUATION
This section evaluates the MOSBENCH applications on
the most recent Linux kernel at the time of writing
(Linux 2.6.35-re5, released on July 12, 2010) and our
modified version of this kernel, PK. For each applica-
tion, we describe how the stock kernel limits scalability,
and how we addressed the bottlenecks by modifying the
application and taking advantage of the PK changes.
Figure 3 summarizes the results of the MOSBENCH
benchmark, comparing application scalability before and
after our modifications. A bar with height 1.0 indicates
perfect scalability (48 cores yielding a speedup of 48).
Most of the applications scale significantly better with
our modifications. All of them fall short of perfect scal-
ability even with those modifications. As the rest of this
section explains, the remaining scalability bottlenecks are
not the fault of the kernel. Instead, they are caused by
non-parallelizable components in the application or un-
derlying hardware: resources that the application’s design
requires it to share, imperfect load balance, or hardware
bottlenecks such as the memory system or the network
card. For this reason, we conclude that the Linux ker-
nel with our modifications is consistent with MOSBENCH
scalability up to 48 cores.
For each application we show scalability plots in the
same format, which shows throughput per core (see, for
example, Figure 4). A horizontal line indicates perfect
1A thread initially busy waits to acquire a mutex, but if the wait time
is long the thread yields the CPU.
USENIX Association
USENIX Association
scalability: each core contributes the same amount of
work regardless of the total number of cores. In practice
one cannot expect a truly horizontal line: a single core
usually performs disproportionately well because there
is no inter-core sharing and because Linux uses a stream-
lined lock scheme with just one core, and the per-chip
caches become less effective as more active cores share
them. For most applications we see the stock kernel’s line
drop sharply because of kernel bottlenecks, and the PK
line drop more modestly.
5.1 Method
We run the applications that modify files on a tmpfs in-
memory file system to avoid waiting for disk I/O. The
result is that MOSBENCH stresses the kernel more it would
if it had to wait for the disk, but that the results are not
representative of how the applications would perform
in a real deployment. For example, a real mail server
would probably be bottlenecked by the need to write each
message durably to a hard disk. The purpose of these
experiments is to evaluate the Linux kernel’s multicore
performance, using the applications to generate a reason-
ably realistic mix of system calls.
We run experiments on a 48-core machine, with a Tyan
Thunder $4985 board and an M4985 quad CPU daughter-
board. The machine has a total of eight 2.4 GHz 6-core
AMD Opteron 8431 chips. Each core has private 64 Kbyte
instruction and data caches, and a 512 Kbyte private L2
cache. The cores on each chip share a 6 Mbyte L3 cache,
1 Mbyte of which is used for the HT Assist probe fil-
ter [7]. Each chip has 8 Gbyte of local off-chip DRAM.
A core can access its L1 cache in 3 cycles, its L2 cache in
14 cycles, and the shared on-chip L3 cache in 28 cycles.
DRAM access latencies vary, from 122 cycles for a core
to read from its local DRAM to 503 cycles for a core to
read from the DRAM of the chip farthest from it on the
interconnect. The machine has a dual-port Intel 82599
10Gbit Ethernet (IXGBE) card, though we use only one
port for all experiments. That port connects to an Ethernet
switch with a set of load-generating client machines.
Experiments that use fewer than 48 cores run with
the other cores entirely disabled. memcached, Apache,
Psearchy, and Metis pin threads to cores; the other ap-
plications do not. We run each experiment 3 times and
show the best throughput, in order to filter out unrelated
activity; we found the variation to be small.
5.2. Exim
To measure the performance of Exim 4.71, we configure
Exim to use tmpfs for all mutable files—spool files, log
files, and user mail files—and disable DNS and RFC1413
lookups. Clients run on the same machine as Exim. Each
repeatedly opens an SMTP connection to Exim, sends 10
separate 20-byte messages to a local user, and closes the
SMTP connection. Sending 10 messages per connection
700 —
I 4500
Stock - - -
PK ——~ 4000
600 PK user time —o—
PK system time —4—
3500
3000
2500
2000
me (usec / message)
1500 2
PU
+ 1000°
Throughput (messages / sec / core)
100 - < sy ——
0 | | | | | | | | | | | 0
12 16 20 24 28 32 36 40 44 48
Cores
500
Figure 4: Exim throughput and runtime breakdown.
prevents exhaustion of TCP client port numbers. Each
client sends to a different user to prevent contention on
user mail files. We use 96 client processes regardless of
the number of active cores; as long as there are enough
clients to keep Exim busy, the number of clients has little
effect on performance.
We modified and configured Exim to increase perfor-
mance on both the stock and PK kernels:
e Berkeley DB v4.6 reads /proc/stat to find the number
of cores. This consumed about 20% of the total run-
time, so we modified Berkeley DB to aggressively
cache this information.
e We configured Exim to split incoming queued mes-
sages across 62 spool directories, hashing by the
per-connection process ID. This improves scala-
bility because delivery processes are less likely to
create files in the same directory, which decreases
contention on the directory metadata in the kernel.
e We configured Exim to avoid an exec() per mail
message, using deliver_drop_privilege.
Figure 4 shows the number of messages Exim can pro-
cess per second on each core, as the number of cores
varies. The stock and PK kernels perform nearly the
same on one core. As the number of cores increases, the
per-core throughput of the stock kernel eventually drops
toward zero. The primary cause of the throughput drop
is contention on a non-scalable kernel spin lock that se-
rializes access to the vfsmount table. Exim causes the
kernel to access the vfsmount table dozens of times for
each message. Exim on PK scales significantly better,
owing primarily to improvements to the vfsmount ta-
ble (Section 4.5) and the changes to the dentry cache
(Section 4.4).
Throughput on the PK kernel degrades from one to
two cores, while the system time increases, because of
the many kernel data structures that are not shared with
one core but must be shared (with cache misses) with
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) 9
10
300000 — 1
T
Stock - - -
PK —
250000 P
200000
150000
100000
Throughput (requests / sec / core)
50000 ;-
1 4 8 12 16 20 24 28 32 36
Cores
40 44 48
Figure 5: memcached throughput.
two cores. The throughput on the PK kernel continues
to degrade; however, this is mainly due to application-
induced contention on the per-directory locks protecting
file creation in the spool directories. As the number of
cores increases, there is an increasing probability that
Exim processes running on different cores will choose the
same spool directory, resulting in the observed contention.
We foresee a potential bottleneck on more cores due
to cache misses when a per-connection process and the
delivery process it forks run on different cores. When
this happens the delivery process suffers caches misses
when it first accesses kernel data—especially data related
to virtual address mappings—that its parent initialized.
The result is that process destruction, which frees virtual
address mappings, and soft page fault handling, which
reads virtual address mappings, execute more slowly with
more cores. For the Exim configuration we use, however,
this slow down is negligible compared to slow down that
results from contention on spool directories.
5.3. memcached
We run a separate memcached 1.4.4 process on each
core to avoid application lock contention. Each server is
pinned to a separate core and has its own UDP port. Each
client thread repeatedly queries a particular memcached
instance for a non-existent key because this places higher
load on the kernel than querying for existing keys. There
are a total of 792 client threads running on 22 client
machines. Requests are 68 bytes, and responses are 64.
Each client thread sends a batch of 20 requests and waits
for the responses, timing out after 100 ms in case packets
are lost.
For both kernels, we use a separate hardware receive
and transmit queue for each core and configure the
IXGBE to inspect the port number in each incoming
packet header, place the packet on the queue dedicated to
the associated memcached’s core, and deliver the receive
interrupt to that core.
Figure 5 shows that memcached does not scale well on
the stock Linux kernel.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
20000 —7J]—7_1_T_1_T_T1_T_T__1_T_ 100
Stock - - -
PK —
PK user time —o—
PK system time —4— _]
80
15000
60
40
CPU time (usec / request)
— 20
Throughput (requests / sec / core)
S
So
S
o
| =
!
pil ae ee et ee
14 8 12 16 20 24 28 32 36 40 44 48
Cores
Figure 6: Apache throughput and runtime breakdown.
One scaling problem occurs in the memory allocator.
Linux associates a separate allocator with each socket to
allocate memory from that chip’s attached DRAM. The
stock kernel allocates each packet from the socket nearest
the PCI bus, resulting in contention on that socket’s allo-
cator. We modified the allocation policy to allocate from
the local socket, which improved throughput by ~30%.
Another bottleneck was false read/write sharing of
IXGBE device driver data in the net_device and
device structures, resulting in cache misses for all cores
even on read-only fields. We rearranged both structures
to isolate critical read-only members to their own cache
lines. Removing a single falsely shared cache line in
net_device increased throughput by 30% at 48 cores.
The final bottleneck was contention on the dst_entry
structure’s reference count in the network stack’s destina-
tion cache, which we replaced with a sloppy counter (see
Section 4.3).
The “PK” line in Figure 5 shows the scalability of
memcached with these changes. The per core throughput
drops off after 16 cores. We have isolated this bottleneck
to the IXGBE card itself, which appears to handle fewer
packets as the number of virtual queues increases. As a
result, it fails to transmit packets at line rate even though
there are always packets queued in the DMA rings.
To summarize, while memcached scales poorly, the
bottlenecks caused by the Linux kernel were fixable and
the remaining bottleneck lies in the hardware rather than
in the Linux kernel.
5.4 Apache
A single instance of Apache running on stock Linux scales
very poorly because of contention on a mutex protecting
the single accept socket. Thus, for stock Linux, we run
a separate instance of Apache per core with each server
running on a distinct port. Figure 6 shows that Apache
still scales poorly on the stock kernel, even with separate
Apache instances.
For PK, we run a single instance of Apache 2.2.14 on
one TCP port. Apache serves a single static file from an
USENIX Association
USENIX Association
ext3 file system; the file resides in the kernel buffer cache.
We serve a file that is 300 bytes because transmitting a
larger file exhausts the available 10 Gbit bandwidth at a
low server core count. Each request involves accepting a
TCP connection, opening the file, copying its content to a
socket, and closing the file and socket; logging is disabled.
We use 58 client processes running on 25 physical client
machines (many clients are themselves multi-core). For
each active server core, each client opens 2 TCP connec-
tions to the server at a time (so, for a 48-core server, each
client opens 96 TCP connections).
All the problems and solutions described in Section 5.3
apply to Apache, as do the modifications to the dentry
cache for both files and sockets described in Section 4.
Apache forks off a process per core, pinning each new pro-
cess to a different core. Each process dedicates a thread
to accepting connections from the shared listening socket
and thus, with the accept queue changes described in Sec-
tion 4.2, each connection is accepted on the core it initially
arrives on and all packet processing is performed local to
that core. The PK numbers in Figure 6 are significantly
better than Apache running on the stock kernel; however,
Apache’s throughput on PK does not scale linearly.
Past 36 cores, performance degrades because the net-
work card cannot keep up with the increasing workload.
Lack of work causes the server idle time to reach 18% at
48 cores. At 48 cores, the network card’s internal diagnos-
tic counters show that the card’s internal receive packet
FIFO overflows. These overflows occur even though the
clients are sending a total of only 2 Gbits and 2.8 million
packets per second when other independent tests have
shown that the card can either receive upwards of 4 Gbits
per second or process 5 million packets per second.
We created a microbenchmark that replicates the
Apache network workload, but uses substantially less
CPU time on the server. In the benchmark, the client ma-
chines send UDP packets as fast as possible to the server,
which also responds with UDP packets. The packet mix
is similar to that of the Apache benchmark. While the mi-
crobenchmark generates far more packets than the Apache
clients, the network card ultimately delivers a similar num-
ber of packets per second as in the Apache benchmark
and drops the rest. Thus, at high core counts, the network
card is unable to deliver additional load to Apache, which
limits its scalability.
5.5 PostgreSQL
We evaluate Linux’s scalability running PostgreSQL 8.3.9
using both a 100% read workload and a 95%/5%
read/write workload. The database consists of a sin-
gle indexed 600 Mbyte table of 10,000,000 key-value
pairs stored in tmpfs. We configure PostgreSQL to use
a 2 Gbyte application-level cache because PostgreSQL
protects its cache free-list with a single lock and thus
25000 —4\ J]. 7. TT TTT o
Stock - - -
Stock + mod PG — - -_ gg
PK + mod PG ——
wa! | user time —O—
g 20000 PK system time —4—~] 70
2 7 e
3 > 60 3
< 15000 3
z 50 3
& =
5 3
& \ 47 400
= 10000 |- \ 5
, s
i + 302
o ; e
E sob \ |”
~~. 10
() A a A 1)
1 4 8 12 16 20 24 28 32 36 40 44 48
Cores
Figure 7: PostgreSQL read-only workload throughput and runtime
breakdown.
25000 -4\—] 7. 7.7 TT TT ”
Stock - - -
Stock + mod PG - - - gg
ee mod PG ——
os user time —O—
2 20000 PK system time —4—= 70
8 B
3 60 2
< 15000 3
Z 50 3
3 ‘ A
& ‘\ = 40
= 10000 - : 5
é a 4 30D
Z ‘ 4d
E sob ee
SO.
S--4 10
(A= AA A A A A AA
1 4 8 12 16 20 24 28 32 36 40 44 48
Cores
Figure 8: PostgreSQL read/write workload throughput and runtime
breakdown.
scales poorly with smaller caches. While we do not pin
the PostgreSQL processes to cores, we do rely on the
IXGBE driver to route packets from long-lived connec-
tions directly to the cores processing those connections.
Our workload generator simulates typical high-
performance PostgreSQL configurations, where middle-
ware on the client machines aggregates multiple client
connections into a small number of connections to the
server. Our workload creates one PostgreSQL connection
per server core and sends queries (selects or updates) in
batches of 256, aggregating successive read-only transac-
tions into single transactions. This workload is intended to
minimize application-level contention within PostgreSQL
in order to maximize the stress PostgreSQL places on the
kernel.
The “Stock” line in Figures 7 and 8 shows that Post-
greSQL has poor scalability on the stock kernel. The first
bottleneck we encountered, which caused the read/write
workload’s total throughput to peak at only 28 cores, was
due to PostgreSQL’s design. PostgreSQL implements
row- and table-level locks atop user-level mutexes; as
a result, even a non-conflicting row- or table-level lock
acquisition requires exclusively locking one of only 16
global mutexes. This leads to unnecessary contention for
non-conflicting acquisitions of the same lock—as seen in
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) il
12
the read/write workload—and to false contention between
unrelated locks that hash to the same exclusive mutex. We
address this problem by rewriting PostgreSQL’s row- and
table-level lock manager and its mutexes to be lock-free
in the uncontended case, and by increasing the number of
mutexes from 16 to 1024.
The “Stock + mod PG” line in Figures 7 and 8 shows
the results of this modification, demonstrating improved
performance out to 36 cores for the read/write workload.
While performance still collapses at high core counts,
the cause of this has shifted from excessive user time to
excessive system time. The read-only workload is largely
unaffected by the modification as it makes little use of
row- and table-level locks.
With modified PostgreSQL on stock Linux, through-
put for both workloads collapses at 36 cores, with sys-
tem time rising from 1.7 jsseconds/query at 32 cores to
322 yuseconds/query at 48 cores. The main reason is the
kernel’s 1seek implementation. PostgreSQL calls lseek
many times per query on the same two files, which in turn
acquires a mutex on the corresponding inode. Linux’s
adaptive mutex implementation suffers from starvation
under intense contention, resulting in poor performance.
However, the mutex acquisition turns out not to be neces-
sary, and PK eliminates it.
Figures 7 and 8 show that, with PK’s modified 1seek
and smaller contributions from other PK changes, Post-
greSQL performance no longer collapses. On PK, Post-
greSQL’s overall scalability is primarily limited by con-
tention for the spin lock protecting the buffer cache page
for the root of the table index. It spends little time in the
kernel, and is not limited by Linux’s performance.
5.6 gmake
We measure the performance of parallel gmake by build-
ing the object files of Linux 2.6.35-rc5 for x86_64. All
input source files reside in the buffer cache, and the output
files are written to tmpfs. We set the maximum number
of concurrent jobs of gmake to twice the number of cores.
Figure 9 shows that gmake on 48 cores achieves ex-
cellent scalability, running 35 times faster on 48 cores
than on one core for both the stock and PK kernels. The
PK kernel shows slightly lower system time owing to the
changes to the dentry cache. gmake scales imperfectly
because of serial stages at the beginning of the build and
straggling processes at the end.
gmake scales so well in part because much of the CPU
time is in the compiler, which runs independently on
each core. In addition, Linux kernel developers have
thoroughly optimized kernel compilation, since it is of
particular importance to them.
5.7 Psearchy/pedsort
Figure 10 shows the runtime for different versions of
pedsort indexing the Linux 2.6.35-rc5 source tree, which
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
7 T T T T 800
Stock - - -
PK ——_|
6 PK user time —o—
PK system time —4—
O- -O-
700
-o-
a
S
S
w
S
S
I I
w £
So i>
o o
CPU time (sec / build)
Throughput (builds / hour / core)
L
vy
S
o
T
L
S
o
A—_- a Le A—_ ae aA
0 | | | | | | | | | |
20 24 28 32 36 40 44 4
Cores
o
oo
Figure 9: gmake throughput and runtime breakdown.
T T T T T T T T T T
Stock + Threads - - - _} 120
50 - Stock + Procs - - —
Stock + Procs RR ———
x RR user time —o—
g RR system time —4—— 100
5 40
2 3s
2 =
2 80 >
3 30K. 3
3 7 4+ oo 2
8 =
& 20- 5
i = 40
10F 4 20
Ap ppp pF
1 4 8 12 16 20 24 28 32 36 40 44 48
Cores
Figure 10: pedsort throughput and runtime breakdown.
consists of 368 Mbyte of text across 33,312 source files.
The input files are in the buffer cache and the output
files are written to tmpfs. Each core uses a 48 Mbyte
word hash table and limits the size of each output index
to 200,000 entries (see Section 3.6). As a result, the
total work performed by pedsort and its final output are
independent of the number of cores involved.
The initial version of pedsort used a single process with
one thread per core. The line marked “Stock + Threads” in
Figure 10 shows that it scales badly. Most of the increase
in runtime is in system time: for | core the system time
is 2.3 seconds, while at 48 cores the total system time is
41 seconds.
Threaded pedsort scales poorly because a per-process
kernel mutex serializes calls to mmap and munmap for a
process’ virtual address space. pedsort reads input files
using libc file streams, which access file contents via
mmap, resulting in contention over the shared address
space, even though these memory-mapped files are logi-
cally private to each thread in pedsort. We avoided this
problem by modifying pedsort to use one process per
core for concurrency, eliminating the mmap contention by
eliminating the shared address space. This modification
involved changing about 10 lines of code in pedsort. The
performance of this version on the stock kernel is shown
as “Stock + Procs” in Figure 10. Even on a single core,
USENIX Association
USENIX Association
the multi-process version outperforms the threaded ver-
sion because any use of threads forces glibc to use slower,
thread-safe variants of various library functions.
With a small number of cores, the performance of the
process version depends on how many cores share the per-
socket L3 caches. Figure 10’s “Stock + Procs” line shows
performance when the active cores are spread over few
sockets, while the “Stock + Procs RR” shows performance
when the active cores are spread evenly over sockets. As
corroborated by hardware performance counters, the latter
scheme provides higher performance because each new
socket provides access to more total L3 cache space.
Using processes, system time remains small, so the ker-
nel is not a limiting factor. Rather, as the number of cores
increases, pedsort spends more time in the glibc sorting
function msort_with_tmp, which causes the decreasing
throughput and rising user time in Figure 10. As the num-
ber of cores increases and the total working set size per
socket grows, msort_with_tmp experiences higher L3
cache miss rates. However, despite its memory demands,
msort_with_tmp never reaches the DRAM bandwidth
limit. Thus, pedsort is bottlenecked by cache capacity.
5.8 Metis
We measured Metis performance by building an inverted
index from a 2 Gbyte in-memory file. As for Psearchy,
we spread the active cores across sockets and thus have
access to the machine’s full L3 cache space at 8 cores.
The “Stock + 4 KB pages” line in Figure 11 shows
Metis’ original performance. As the number of cores
increases, the per-core performance of Metis decreases.
Metis allocates memory with mmap, which adds the new
memory to a region list but defers modifying page ta-
bles. When a fault occurs on a new mapping, the kernel
locks the entire region list with a read lock. When many
concurrent faults occur on different cores, the lock itself
becomes a bottleneck, because acquiring it even in read
mode involves modifying shared lock state.
We avoided this problem by mapping memory with
2 Mbyte super-pages, rather than 4 Kbyte pages, using
Linux’s hugetlbfs. This results in many fewer page
faults and less contention on the region list lock. We
also used finer-grained locking in place of a global mutex
that serialized super-page faults. The “PK + 2MB pages’
line in Figure 11 shows that use of super-pages increases
performance and significantly reduces system time.
>
With super-pages, the time spent in the kernel becomes
negligible and Metis’ scalability is limited primarily by
the DRAM bandwidth required by the reduce phase. This
phase is particularly memory-intensive and, at 48 cores,
accesses DRAM at 50.0 Gbyte/second, just shy of the
maximum achievable throughput of 51.5 Gbyte/second
measured by our microbenchmarks.
35 TTt~T-T_T—1 200
Stock + 4KB pages - - -
PK + 2MB pages
Stock user time - 4-
PK user time —o—
‘stem time —4— _|
30
25
!
S
Ss
CPU time (sec / job)
= 50
Throughput (jobs / hour / core)
=F ae}
-Aa-"
ws ae Soke
AA A RR ee
14 8 12 16 20 24 28 32 36 40 44 48
Cores
Figure 11: Metis throughput and runtime breakdown.
Application | Bottleneck
Exim App: Contention on spool directories
memcached | HW: Transmit queues on NIC
Apache HW: Receive queues on NIC
PostgreSQL | App: Application-level spin lock
gmake App: Serial stages and stragglers
pedsort HW: Cache capacity
Metis HW: DRAM throughput
Figure 12: Summary of the current bottlenecks in MOSBENCH, at-
tributed either to hardware (HW) or application structure (App).
5.9 Evaluation summary
Figure 3 summarized the significant scalability improve-
ments resulting from our changes. Figure 12 summarizes
the bottlenecks that limit further scalability of MOSBENCH
applications. In each case, the application is bottle-
necked by either shared hardware resources or application-
internal scalability limits. None are limited by Linux-
induced bottlenecks.
6 DISCUSSION
The results from the previous section show that the MOS-
BENCH applications can scale well to 48 cores, with mod-
est changes to the applications and to the Linux kernel.
Different applications or more cores are certain to reveal
more bottlenecks, just as we encountered bottlenecks at
48 cores that were not important at 24 cores. For exam-
ple, the costs of thread and process creation seem likely
to grow with more cores in the case where parent and
child are on different cores. Given our experience scaling
Linux to 48 cores, we speculate that fixing bottlenecks
in the kernel as the number of cores increases will also
require relatively modest changes to the application or
to the Linux kernel. Perhaps a more difficult problem is
addressing bottlenecks in applications, or ones where ap-
plication performance is not bottlenecked by CPU cycles,
but by some other hardware resource, such as DRAM
bandwidth.
Section 5 focused on scalability as a way to increase
performance by exploiting more hardware, but it is usu-
ally also possible to increase performance by exploiting
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) 13
a fixed amount of hardware more efficiently. Techniques
that a number of recent multicore research operating sys-
tems have introduced (such as address ranges, dedicating
cores to functions, shared memory for inter-core message
passing, assigning data structures carefully to on-chip
caches, etc. [11, 15, 53]) could apply equally well to
Linux, improving its absolute performance and benefiting
certain applications. In future work, we would like to
explore such techniques in Linux.
One benefit of using Linux for multicore research is that
it comes with many applications and has a large developer
community that is continuously improving it. However,
there are downsides too. For example, if future processors
don’t provide high-performance cache coherence, Linux’s
shared-memory-intensive design may be an impediment
to performance.
7 CONCLUSION
This paper analyzes the scaling behavior of a traditional
operating system (Linux 2.6.35-rce5) on a 48-core com-
puter with a set of applications that are designed for par-
allel execution and use kernel services. We find that we
can remove most kernel bottlenecks that the applications
stress by modifying the applications or kernel slightly.
Except for sloppy counters, most of our changes are ap-
plications of standard parallel programming techniques.
Although our study has a number of limitations (e.g., real
application deployments may be bottlenecked by I/O), the
results suggest that traditional kernel designs may be com-
patible with achieving scalability on multicore comput-
ers. The MOSBENCH applications are publicly available
at http: //pdos.csail.mit.edu/mosbench/, so that
future work can investigate this hypothesis further.
ACKNOWLEDGMENTS
We thank the anonymous reviewers and our shepherd,
Brad Chen, for their feedback. This work was partially
supported by Quanta Computer and NSF through award
numbers 0834415 and 0915164. Silas Boyd-Wickizer is
partially supported by a Microsoft Research Fellowship.
Yandong Mao is partially supported by a Jacobs Presi-
dential Fellowship. This material is based upon work
supported under a National Science Foundation Graduate
Research Fellowship.
REFERENCES
[1] Apache HTTP Server, May 2010.
httpd.apache.org/.
http://
[2] Exim, May 2010. http: //www.exim.org/.
[3] Memcached, May http://
memcached.org/.
2010.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
[4] PostreSQL, May 2010.
www.postgresql.org/.
http://
[5] The search for fast, scalable counters, May 2010.
http://lwn.net/Articles/170003/.
[6] J. Aas. Understanding the Linux 2.6.8.1
CPU scheduler, February 2005. http://
josh. trancesoftware.com/linux/.
[7] AMD, Inc. Six-core AMD opteron processor
features. http://www.amd.com/us/products/
server/processors/six-core-opteron/
Pages/six-core-opteron-key-architectural
-features.aspx.
[8] T. E. Anderson, B. N. Bershad, E. D. Lazowska,
and H. M. Levy. Scheduler activations: Effective
kernel support for the user-level management of
parallelism. In Proc. of the 13th SOSP, pages 95—
109, 1991.
[9] J. Appavoo, D. D. Silva, O. Krieger, M. Auslander,
M. Ostrowski, B. Rosenburg, A. Waterland, R. W.
Wisniewski, J. Xenidis, M. Stumm, and L. Soares.
Experience distributing objects in an SMMP OS.
ACM Trans. Comput. Syst., 25(3):6, 2007.
[10] K. Asanovic, R. Bodik, J. Demmel, T. Keaveny,
K. Keutzer, J. Kubiatowicz, N. Morgan, D. Pat-
terson, K. Sen, J. Wawrzynek, D. Wessel, and
K. Yelick. A view of the parallel computing land-
scape. Commun. ACM, 52(10):56-67, 2009.
[11] A. Baumann, P. Barham, P.-E. Dagand, T. Haris,
R. Isaacs, S. Peter, T. Roscoe, A. Schiipbach, and
A. Singhania. The Multikernel: a new OS architec-
ture for scalable multicore systems. In Proc of the
22nd SOSP, Big Sky, MT, USA, Oct 2009.
[12] B. N. Bershad, T. E. Anderson, E. D. Lazowska,
and H. M. Levy. Lightweight remote procedure call.
ACM Trans. Comput. Syst., 8(1):37-55, 1990.
[13] D. L. Black. Scheduling support for concurrency
and parallelism in the Mach operating system. Com-
puter, 23(5):35-43, 1990.
[14] W. Bolosky, R. Fitzgerald, and M. Scott. Simple but
effective techniques for NUMA memory manage-
ment. In Proc. of the 12th SOSP, pages 19-31, New
York, NY, USA, 1989. ACM.
[15] S. Boyd-Wickizer, H. Chen, R. Chen, Y. Mao,
F. Kaashoek, R. Morris, A. Pesterev, L. Stein,
M. Wu, Y. D. Y. Zhang, and Z. Zhang. Corey: An
operating system for many cores. In Proc. of the 8th
OSDI, December 2008.
USENIX Association
[16] R. Bryant, J. Hawkes, J. Steiner, J. Barnes, and
[17
[18
[19
[20
[21
[22
[23
[24
—
“4
]
=
]
—“
]
“4
—
USENIX Association
J. Higdon. Scaling linux to the extreme. In Proceed-
ings of the Linux Symposium 2004, pages 133-148,
Ottawa, Ontario, June 2004.
B. Cantrill and J. Bonwick. Real-world concurrency.
Commun. ACM, 51(11):34—39, 2008.
J. Corbet. The lockless page cache, May 2010.
http://lwn.net/Articles/291826/.
A. L. Cox and R. J. Fowler. The implementation of
a coherent memory abstraction on a NUMA multi-
processor: Experiences with platinum. In Proc. of
the 12th SOSP, pages 32-44, 1989.
J. Dean and S. Ghemawat. MapReduce: simplified
data processing on large clusters. Commun. ACM,
51(1):107-113, 2008.
M. Dobrescu, N. Egi, K. Argyraki, B.-G. Chun,
K. Fall, G. Iannaccone, A. Knies, M. Manesh, and
S. Ratnasamy. RouteBricks: Exploiting parallelism
to scale software routers. In Proc of the 22nd SOSP,
Big Sky, MT, USA, Oct 2009.
F. Ellen, Y. Lev, V. Luchango, and M. Moir. SNZI:
Scalable nonzero indicators. In PODC 2007, Port-
land, Oregon, USA, Aug. 2007.
GNU Make, May 2010. http://www. gnu.org/
software/make/.
C. Gough, S. Siddha, and K. Chen. Kernel
scalability—expanding the horizon beyond fine
grain locks. In Proceedings of the Linux Sympo-
sium 2007, pages 153-165, Ottawa, Ontario, June
2007.
T. Herbert. rfs: receive flow steering, September
2010. http: //lwn.net/Articles/381955/.
T. Herbert. rps: receive packet steering, September
2010. http: //lwn.net/Articles/361440/.
M. Herlihy. Wait-free synchronization. ACM Trans.
Program. Lang. Syst., 13(1):124-149, 1991.
J. Jackson. Multicore requires OS rework
Windows architect advises. PCWorld mag-
azine, 2010. http: //www.pcworld.com/
businesscenter/article/191914/
multicore_requires_os_rework_windows
_architect_advises.html.
Z. Jia, Z. Liang, and Y. Dai. Scalability evaluation
and optimization of multi-core SIP proxy server. In
Proc. of the 37th ICPP, pages 43-50, 2008.
[30]
[31]
=
Oo
NO
foes
[33
“4
[34]
[39]
[41
ra
[42]
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
A. R. Karlin, K. Li, M. S. Manasse, and S. S. Ow-
icki. Empirical studies of competitive spinning for a
shared-memory multiprocessor. In Proc. of the 13th
SOSP, pages 41-55, 1991.
A. Kleen. An NUMA API for Linux, August
2004. http://www. firstfloor.org/~ andi/
numa.html.
A. Kleen. Linux multi-core scalability. In Proceed-
ings of Linux Kongress, October 2009.
J. Kuskin, D. Ofelt, M. Heinrich, J. Heinlein, R. Si-
moni, K. Gharachorloo, J. Chapin, D. Nakahira,
J. Baxter, M. Horowitz, A. Gupta, M. Rosenblum,
and J. Hennessy. The Stanford FLASH multipro-
cessor. In Proc. of the 21st ISCA, pages 302-313,
1994.
R. P. LaRowe, Jr., C. S. Ellis, and L. S. Kaplan.
The robustness of NUMA memory management. In
Proc. of the 13th SOSP, pages 137-151, 1991.
J. Li, B. T. Loo, J. M. Hellerstein, M. F. Kaashoek,
D. Karger, and R. Morris. On the feasibility of peer-
to-peer web indexing and search. In Proc. of the 2nd
IPTPS, Berkeley, CA, February 2003.
Linux 2.6.35-rce5 source, July
2010. Documentation/scheduler/
sched-design-CFS.txt.
Linux kernel mailing list, May 2010. http://
kerneltrap.org/node/8059.
Y. Mao, R. Morris, and F. Kaashoek. Optimizing
MapReduce for multicore architectures. Technical
Report MIT-CSAIL-TR-2010-020, MIT, 2010.
P. E. McKenney, D. Sarma, A. Arcangeli, A. Kleen,
O. Krieger, and R. Russell. Read-copy update. In
Proceedings of the Linux Symposium 2002, pages
338-367, Ottawa, Ontario, June 2002.
P. E. McKenney, D. Sarma, and M. Soni. Scal-
ing dcache with reu, Jan. 2004. http://
www. linuxjournal.com/article/7124.
J. M. Mellor-Crummey and M. L. Scott. Algorithms
for scalable synchronization on shared-memory mul-
tiprocessors. ACM Trans. Comput. Syst., 9(1):21-65,
1991.
E. M. Nahum, D. J. Yates, J. F Kurose, and
D. Towsley. Performance issues in parallelized net-
work protocols. In Proc. of the Ist OSDI, page 10,
Berkeley, CA, USA, 1994. USENIX Association.
15
[43] D. Patterson. The parallel revolution has started:
[44]
[45
[46
[47
[48
[49
[50
[51
]
]
]
“4
—
]
ra
are you part of the solution or the prolem? In
USENIX ATEC, 2008. www.usenix.org/event/
usenix08/tech/slides/patterson. pdf.
A. Pesterev, N. Zeldovich, and R. T. Morris. Lo-
cating cache performance bottlenecks using data
profiling. In Proceedings of the ACM EuroSys Con-
ference (EuroSys 2010), Paris, France, April 2010.
C. Ranger, R. Raghuraman, A. Penmetsa, G. Brad-
ski, and C. Kozyrakis. Evaluating MapReduce for
multi-core and multiprocessor system. In Proceed-
ings of HPCA. IEEE Computer Society, 2007.
C. Schimmel. UNIX systems for modern architec-
tures: symmetric multiprocessing and caching for
kernel programmers. Addison-Wesley, 1994.
M. D. Schroeder and M. Burrows. Performance
of Firefly RPC. In Proc. of the 12th SOSP, pages
83-90, 1989.
J. Stribling, J. Li, I. G. Councill, M. F. Kaashoek,
and R. Morris. Overcite: A distributed, cooperative
citeseer. In Proc. of the 3rd NSDI, San Jose, CA,
May 2006.
J.H. Tseng, H. Yu, S. Nagar, N. Dubey, H. Franke,
P. Pattnaik, H. Inoue, and T. Nakatani. Performance
studies of commercial workloads on a multi-core
system. IEEE Workload Characterization Sympo-
sium, pages 57-65, 2007.
R. Vaswani and J. Zahorjan. The implications of
cache affinity on processor scheduling for multipro-
grammed, shared memory multiprocessors. In Proc.
of the 13th SOSP, pages 26-40, 1991.
B. Veal and A. Foong. Performance scalability of
a multi-core web server. In Proceedings of the 3rd
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
[52
[55
[56
“4
]
]
ss
ACM/IEEE Symposium on Architecture for Network-
ing and Communications Systems, pages 57-66,
New York, NY, USA, 2007.
B. Verghese, S. Devine, A. Gupta, and M. Rosen-
blum. Operating system support for improving data
locality on CC-NUMA compute servers. In Proc.
of the 7th ASPLOS, pages 279-289, New York, NY,
USA, 1996. ACM.
D. Wentzlaff and A. Agarwal. Factored operating
systems (fos): the case for a scalable operating
system for multicores. SIGOPS Oper. Syst. Rev.,
43(2):76-85, 2009.
C. Yan, Y. Chen, and S. Yuanchun. Parallel scalabil-
ity comparison of commodity operating systems on
large scale multi-cores. In Proceedings of the work-
shop on the interaction between Operating Systems
and Computer Architecture (WIOSCA 2009).
C. Yan, Y. Chen, and S. Yuanchun. OSMark: A
benchmark suite for understanding parallel scalabil-
ity of operating systems on large scale multi-cores.
In 2009 2nd International Conference on Computer
Science and Information Technology, pages 313-
317, 2009.
C. Yan, Y. Chen, and S. Yuanchun. Scaling OLTP
applications on commodity multi-core platforms.
In 2010 IEEE International Symposium on Perfor-
mance Analysis of Systems & Software (ISPASS),
pages 134-143, 2010.
M. Young, A. Tevanian, R. F. Rashid, D. B. Golub,
J. L. Eppinger, J. Chew, W. J. Bolosky, D. L. Black,
and R. V. Baron. The duality of memory and commu-
nication in the implementation of a multiprocessor
operating system. In Proc. of the 11th SOSP, pages
63-76, 1987.
USENIX Association
USENIX Association
Trust and Protection in the Illinois Browser Operating System
Shuo Tang, Haohui Mai, Samuel T. King
University of Illinois at Urbana-Champaign
Abstract
Current web browsers are complex, have enormous
trusted computing bases, and provide attackers with easy
access to modern computer systems. In this paper we in-
troduce the Illinois Browser Operating System (IBOS),
a new operating system and a new browser that re-
duces the trusted computing base for web browsers. In
our architecture we expose browser-level abstractions
at the lowest software layer, enabling us to remove al-
most all traditional OS components and services from
our trusted computing base by mapping browser abstrac-
tions to hardware abstractions directly. We show that this
architecture is flexible enough to enable new browser se-
curity policies, can still support traditional applications,
and adds little overhead to the overall browsing experi-
ence.
1 Introduction
Web-based applications (web apps), browsers, and op-
erating systems have become popular targets for attack-
ers of computer systems. Vulnerabilities in web apps
are widespread and increasing. For example, cross-site
scripting (XSS), which is effectively a form of script in-
jection into a web app, recently overtook the ubiquitous
buffer overflow as the most common security vulnerabil-
ity [50]. Vulnerabilities in web browsers are less com-
mon than web app vulnerabilities, but still occur often.
For example, in 2009 Internet Explorer, Chrome, Safari,
and Firefox had 349 new security vulnerabilities [4], and
attackers exploit browsers commonly [53, 37, 42, 41, 4].
Vulnerabilities in libraries, system services, and oper-
ating systems are less common than vulnerabilities in
browsers, but are still problematic for modern systems.
For example, glibc, GTK+, X, and Linux had 114 new
security vulnerabilities in 2009 [1], and in 2009 the most
commonly attacked vulnerability was a remote code ex-
ecution bug in the Windows kernel [4].
However, not all attacks on web apps, browsers, and
operating systems are equally virulent. At the top of the
computer stack, attacks on web apps, such as XSS, oper-
ate within current browser security policies that contain
the damage to the vulnerable web app. Moving down
the computer stack, attacks on browsers can cause more
damage because a successful attack gives the attacker ac-
cess to browser data for all web apps and access to other
resources on the system. At the lowest layers of the
computer stack, attacks on libraries, shared system ser-
vices, and operating systems are the most serious attacks
because attackers can access arbitrary states and events,
giving them complete control of the system.
Overall, these trends indicate that vulnerabilities
higher in the computer stack are more common, but vul-
nerabilities lower in the computer stack provide attack-
ers with more control and are more damaging. In this
paper we focus on preventing and containing attacks on
browsers, libraries, system services, and operating sys-
tems — the lower layers of the computer stack.
Current research efforts into more secure web
browsers help improve the security of browsers, but
remain susceptible to attacks on lower layers of the
computer stack. The OP web browser [26], Gazelle
[52], Chrome [11], and ChromeOS [25] propose new
browser architectures for separating the functionality
of the browser from security mechanisms and policies.
However, these more secure web browsers are all built
on top of commodity operating systems and include
complex user-mode libraries and shared system services
within their trusted computing base (TCB). Even kernel
designs with strong isolation between OS components
(e.g., microkernels [24, 27, 28] and information-flow ker-
nels [18, 57, 33]) still have OS services that are shared
by all applications, which attackers can compromise and
still cause damage. Here are a few ways that an attacker
can still cause damage to more secure web browsers built
on top of traditional OSes:
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) 17
18
e A compromised Ethernet driver can send sensitive
HTTP data (e.g., passwords or login cookies) to any
remote host or change the HTTP response data be-
fore routing it to the network stack.
e Acompromised storage module can modify or steal
any browser related persistent data.
e A compromised network stack can tamper with any
network connection or send sensitive HTTP data to
an attacker.
e A compromised window manager can draw any
content on top of a web page to deploy visual at-
tacks, such as phishing.
In this paper we describe IBOS, an operating sys-
tem and a browser co-designed to reduce drastically the
TCB for web browsers and to simplify browser-based
systems. Our key insight is that our lowest-layer soft-
ware can expose browser-level abstractions, rather than
general-purpose OS abstractions, to provide vastly im-
proved security properties for the browser without affect-
ing the TCB for traditional applications. Some examples
of browser abstractions are cookies for persistent storage,
hypertext transfer protocol (HTTP) connections for net-
work I/O, and tabs for displaying web pages. To support
traditional applications, we build UNIX-like abstractions
on top of our browser abstractions.
IBOS improves on past approaches by removing typi-
cally shared OS components and system services from
our browser’s TCB, including device drivers, network
protocol implementations, the storage stack, and win-
dow management software. All of these components run
above a trusted reference monitor [9], which enforces our
security policies. These components operate on browser-
level abstractions, allowing us to map browser security
policies down to the lowest-level hardware directly and
to remove drivers and system services from our TCB.
This architecture is a stark contrast to current systems
where all applications layer application-specific abstrac-
tions on top of general-purpose OS abstractions, inherit-
ing the cruft needed to implement and access these gen-
eral OS abstractions. By exposing application-specific
abstractions at the OS layer, we can cut through complex
software layers for one particular application without af-
fecting traditional applications adversely, which still run
on top of general OS abstractions and still inherit cruft.
We choose to illustrate this principle using a web browser
because browsers are used widely and have been prone
to security failures recently. Our goal is to build a sys-
tem where a user can visit a trusted web site safely, even
one or more of the components on the system have been
compromised.
Our contributions are:
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
e IBOS is the first system to improve browser and OS
security by making browser-level abstractions first-
class OS abstractions, providing a clean separation
between browser functionality and browser security.
e We show that having low-layer software expose
browser abstractions enables us to remove almost
all traditional OS components from our TCB, in-
cluding device drivers and shared OS services, al-
lowing IBOS to withstand a wide range of attacks.
e We demonstrate that IBOS can still support tradi-
tional applications that interact with the browser and
shared OS services without compromising the secu-
rity of our system.
2 The IBOS architecture
This paper presents the design and implementation of
the IBOS operating system and browser that reduce the
TCB for browsing drastically. Our primary goals are to
enforce today’s browser security policies with a small
TCB, without restricting functionality, and without slow-
ing down performance. To withstand attacks, IBOS must
ensure any compromised component (1) cannot tamper
with data it should not have access to, (2) cannot leak
sensitive information to third parties, and (3) cannot ac-
cess components operating on behalf of different web
sites.
In this section we discuss the design principles that
guide our design and the overall system architecture. In
Section 4 we discuss the security policies and mecha-
nisms we use.
2.1 Design principles
We embrace microkernel [27], Exokernel [19], and
safety kernel design principles in our overall architec-
ture. By combining these principles with our insight
about exposing browser abstractions at the lowest soft-
ware layer we hope to converge on a more trustworthy
browser design. Five key principles guide our design:
1. Make security decisions at the lowest layer of soft-
ware. By pushing our security decisions to the low-
est layers we hope to avoid including the millions
of lines of library and OS code in our TCB.
2. Use controlled sharing between web apps and tra-
ditional apps. Sharing data between web apps and
traditional apps is a fundamental functionality of
today’s practical systems and should be supported.
However, this sharing should be facilitated through
a narrow interface to prevent misuse.
USENIX Association
USENIX Association
‘ uiuc. edu!
‘maps. com! : bing. com '
: ‘ localhost ;
Traditional
Applications
UNIX Layer
'
Browser abstractions
= Storage Net. Processes erases
Instance Instance Instance
Figure 1: Overall IBOS architecture. Our system con-
tains user-mode drivers, browsers API managers, web
page instances, and traditional processes. To manage the
interactions between these components, we use a refer-
ence monitor that runs within our IBOS kernel. Shaded
regions make up the TCB.
3. Maintain compatibility with current browser secu-
rity policies. Our primary goal is to improve the
enforcement of current browser policies without
changing current web-based applications.
4. Expose enough browser states and events to enable
new browser security policies. In addition to en-
forcing current browser policies, we would like our
architecture to adapt easily to future browser poli-
cies.
5. Avoid rule-based OS sandboxing for browser com-
ponents. Fundamentally, rule-based OS sandbox-
ing is about restricting unused or overly permis-
sive interfaces exposed by today’s operating sys-
tems. However, sandboxing systems can be com-
plex (the Ubuntu 10.04 SELinux reference policy
uses over 104K lines of policy code) and difficult to
implement correctly [23, 51]. If our architecture re-
quires OS sandboxing for browser components then
we should rethink the architecture.
2.2 Overall architecture
Figure 1 shows the overall IBOS architecture. The IBOS
architecture uses a basic microkernel approach with a
thin kernel for managing hardware and facilitating mes-
sage passing between processes. The system includes
user-mode device drivers for interacting directly with
hardware devices, such as network interface cards (NIC),
and browser API managers for accessing the drivers and
implementing browser abstractions. The key browser
abstractions that the browser API managers implement
are HTTP requests, cookies and local storage for stor-
ing persistent data, and tabs for displaying user-interface
(UI) content. Web apps use these abstractions directly
to implement browser functionality, and traditional ap-
plications (traditional apps) use a UNIX layer to access
UNIX-like abstractions on top of these browser abstrac-
tions.
2.2.1 The IBOS kernel
Our IBOS kernel is the software TCB for the browser and
includes resource management functionality and a refer-
ence monitor for security enforcement. The IBOS kernel
also handles many traditional OS tasks such as manag-
ing global resources, creating new processes, and man-
aging memory for applications. To facilitate message
passing, the IBOS kernel includes the L4Ka::Pistachio
[8] message passing implementation and MMU manage-
ment functions. All messages pass through our reference
monitor and are subjected to our overall system security
policy. Section 4 describes the policies that the IBOS
kernel enforces and the mechanisms it uses to implement
these policies.
2.2.2 Network, storage, and UI managers
The IBOS network subsystem handles HTTP requests
and socket calls for applications. To handle HTTP re-
quests, network processes check a local cache to see if
the request can be serviced via the cache, fetch any cook-
ies needed for the request, format the HTTP data into a
TCP stream, and transform that TCP stream into a series
of Ethernet frames that are sent to the NIC driver. Socket
network processes export a basic socket API and simply
transform TCP streams to Ethernet frames for transmis-
sion across the network. Only traditional apps can access
our socket network processes. The IBOS kernel manages
global states, like port allocation.
The IBOS storage manager maintains persistent stor-
age for key-value data pairs. The browser uses the stor-
age manager to store HTTP cookies and HTMLS local
storage objects, and the basic object store includes op-
tional parameters, such as Path and Max—Age, to ex-
pose cookie properties to the reference monitor. The
storage manager uses several different namespaces to
isolate objects from each other. Web apps and net-
work processes share a namespace based on the origin
(the <protocol, domain name, port> tuple of
a uniform resource locator) that they originate from,
and web apps and traditional apps share a “localhost”
namespace, which is separate from the HTTP names-
pace. All other drivers and managers have their own pri-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) 19
20
vate namespaces to access persistent data.
The IBOS UI manager plays the role of the window
manager for the system. However, rather than implement
the browser UI components on top of the traditional win-
dow motif, we opted for a tabbed browser motif. Basic
browser UI widgets, called the browser chrome, are dis-
played at the top of the screen. IBOS displays web pages
in tabs and the user can have any number of tabs open for
web apps. There is a tab for basic browser configuration
and administration, and a tab that is shared by traditional
apps. If traditional apps wish to implement the window
motif, they can do so within the tab. The main advan-
tage of our browser-based motif is that it enables IBOS
to bypass the extra layers of indirection traditional win-
dow managers put between applications and the under-
lying graphics hardware, exposing browser UI elements
and events directly to the IBOS kernel. We discuss the
security implications of our design decision in more de-
tail in Section 4.8.
2.2.3. Web apps, traditional apps, and plugins
The IBOS system supports two different types of pro-
cesses: web page instances and traditional processes. A
web page instance is a process that is created for each in-
dividual web page a user visits. Each time the user clicks
on a link or types a uniform resource locator (URL) into
the address bar, the IBOS kernel creates a new web page
instance. Web page instances are responsible for issuing
HTTP requests, parsing HTML, executing JavaScript,
and rendering web content to a tab. Traditional processes
can execute arbitrary instructions, and the key difference
between a web page instance and a traditional processes
is that the IBOS kernel gives them different security la-
bels, which the kernel uses for access control decisions.
Web page instances are labeled with the origin of the
HTTP request used to initiate the new web page, and tra-
ditional processes are labeled as being from “localhost.”
These two processes interact via the storage subsystem
since both types of processes can access “localhost” data.
In general, plugins are external applications that
browsers use to render non-HTML content. One com-
mon example of a plugin is the Flash player that enables
browsers to play Flash content. In IBOS, plugins run as
traditional processes, except that they are launched by
the browser and the system gives them access to browser
states and events through a standard plugin programming
interface, called the NPAPI [2].
3 Current browser policies
In this section we give a brief introduction to the same-
origin policy (SOP) for browser security. For a more
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
complete discussion of this policy and others, plus exper-
imental results showing how current browsers implement
them, please see a recent paper by Singh, et al. [47].
The primary security policy that all modern browsers
implement is the SOP. The SOP acts as a non-
interference policy for the web. Loosely speaking, the
SOP provides isolation for web pages and states that
come from different origins — origins are used as labels
for browser access control policies. If the browser has a
web page open from uiuc.edu and from attacker.
com, the SOP should ensure that these two web pages are
isolated from each other. Unfortunately, Chrome, IE8,
Safari, and Firefox all enforce the SOP using a number
of checks scattered throughout the millions of lines of
browser code and current browsers have had trouble im-
plementing the SOP correctly [14].
In a browser, a frame is a container that encapsulates
a HTML document and any material included in that
HTML document. Web pages are frames, and web de-
velopers can embed additional frames within web pages
— these frames are called iframes. Developers can
include iframes from the same origin as the hosting
frame, or from a different origin. Each frame is labeled
with the origin of the main HTML document used to pop-
ulate the frame, meaning that a cross-origin if rame has
a different label than the hosting web page.
In general HTML documents include references to
network objects that the browser will download and dis-
play to form the web page. These network objects can
be images, JavaScript, and CSS. Browsers can download
these objects from any domain and the browser labels
them with the origin of the hosting frame. For exam-
ple, if a page from uiuc.edu includes a script from
foo.com, that script runs with full uiuc.edu per-
missions and can access any of the states in that web
page. Browsers can also download HTML documents
and XML HTTP requests (used for Ajax), but the SOP
dictates that these objects must come from the same ori-
gin as the hosting frame.
4 IBOS security policies and mechanisms
Our primary goal is to enforce browser security policies
from within our IBOS kernel. This section describes the
mechanisms that the IBOS kernel uses to enforce the
SOP. We also discuss policies and mechanisms for en-
forcing UI interactions, and we describe a custom policy
engine that lets web sites further restrict current policies.
4.1 Threat model and assumptions
Our primary goal is to ensure that the IBOS kernel up-
holds our security policies even if one or more of the sub-
systems have been compromised. In our threat model,
USENIX Association
USENIX Association
Figure 2: This figure enlarges the right half of Figure 1
and shows how our IBOS subsystems interact when a
web page instance from uiuc.edu issues a network
request to foo.com. Subsystems are shown in boxes
and solid and dotted arrows represent IBOS messages for
outgoing and incoming data respectively. The reference
monitor (which is not shown here) checks all these mes-
sages to enforce security properties.
we assume that an attacker controls a web site and can
serve arbitrary data to our browser, or that the system
contains a malicious traditional app. We also assume that
this malicious data or traditional app can compromise
one or more of the components in our system. These
susceptible components include all drivers, browser API
managers, web page instances, and traditional processes.
Once the attacker takes control of these components, we
assume that he or she can execute arbitrary instructions
as a result of the attack. We focus on maintaining the in-
tegrity and confidentiality of the data in our browser. In
other words, we would like the user to be able to open a
web page on a trusted web server, and interact with this
web page securely, even if everything on the client sys-
tem outside of our TCB has been compromised. Avail-
ability is an important, but separate, aspect of browser
security that we do not address in this paper.
In our system we trust the layers upon which we built
IBOS. These layers include the IBOS kernel and the un-
derlying hardware. Like all other browsers, IBOS pred-
icates security decisions based on domain names, so we
trust domain name servers to map domain names to IP
addresses correctly. Compromising any of these trusted
layers compromises the security of IBOS.
4.2 IBOS work flow
This section describes a web page instance making a net-
work request to help illustrate the security mechanisms
that IBOS uses.
Figure 2 shows the flow of how a web page instance
fetches data from the network. The user visits a page
hosted at uiuc.edu and this web page includes an im-
age from foo. com. To download the image, (1) the web
page instance will make an HTTP request that the IBOS
kernel forwards to an appropriate network process. The
network process forms a HTTP request, which includes
setting up HTTP headers, (2) fetching cookies from the
storage subsystem, (3) requesting a free local TCP port
to transform this request into TCP/IP packets and Ether-
net frames, and (4) sending it to network manager. The
network manager notifies the Ethernet driver which (5)
programs the NIC to transmits the packet out to the net-
work. When the NIC receives a reply for the request, (6)
it notifies the Ethernet driver. The driver subsequently
(7) notifies the network manager, which (8) forwards the
packet to the appropriate network process. The network
process then parses the data and (9) passes the resulting
HTTP reply and data to the original web page instance.
4.3 IBOS labels
To enforce access control decisions, the IBOS kernel la-
bels web page instances, traditional processes, and net-
work processes. IBOS labels specify the resources that
a process can access or messages it can receive. Each
web page instance has one label, which is the origin of
the main HTML document. Each traditional process is
labeled as being from “localhost” when they are created.
Each network process has an origin label for the network
resources it handles and has an origin label for the web
page instances that are allowed to access it. IBOS la-
bels the processes upon creation, and keeps the labels
unchanged throughout the processes’ life-cycle.
An important point is that the IBOS kernel infers the
origin labels for web page instances and network pro-
cesses automatically by extracting related information
from the messages passed among them. By inferring la-
bels rather than relying on processes to label themselves,
the IBOS kernel ensures that it has the correct label in-
formation, even if a process is compromised.
The newUrl and fetchUrl IBOS system calls are the
two requests that cause the kernel to label processes. The
newUrl system call is used by web page instances and the
UI manager use to navigate the browser to a new URL.
The newUrl system call consists of two arguments: a
URL and a byte array for HTTP POST data. When the
IBOS kernel receives a newUrl request it will create a
new web page instance and set the label for this web page
instance by parsing the origin out of the URL argument
of the newUrl request. When servicing newUrl requests,
the IBOS kernel will reuse old web page instances (to
reduce process startup times), but only when the origin
labels match for the old web page instance and the URL
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) 21
22
argument.
Web page instances use the fetchUrl system call to is-
sue HTTP and HTTPS requests to fetch network objects,
such as images. The fetchUrl system call has two ar-
guments: a URL and HTTP header information. When
a web page instance issues a fetchUrl system call, the
IBOS kernel uses the origin of the web page instance
(set by the original newUrl call) and the origin of the
fetchUrl URL argument to find a network process with
these same labels, or creates a new network processes
and labels it accordingly if an existing network process
cannot be found.
More details about how we use these labels for access
control decisions are described in the remainder of this
section.
4.4 Security invariants
For all of our subsystems, we use security invariants that
are assertions on all interactions between subsystems that
check basic security properties. The key to our security
invariants is that we can extract security relevant infor-
mation from messages automatically, and provide high
assurance that the system maintains the security policy
without having to understand how each individual sub-
system is implemented. Using these security invariants,
we remove from the TCB almost all of the components
found in modern commodity operating systems, includ-
ing device drivers.
The ideal security invariant is complete, implementa-
tion agnostic, executes quickly, and requires only a small
amount of code in the IBOS kernel. A complete invariant
can infer all of the states needed to ensure the high-level
security policy, and an implementation agnostic invari-
ant can infer states without relying on the specific imple-
mentation of individual subsystems. The IBOS kernel
evaluates invariants in the kernel and inline with mes-
sages, so security invariants should execute quickly and
require little code to implement. In our design we strive
to make the appropriate trade offs among these proper-
ties to improve security without making the system slow
or increasing our TCB significantly. The base security
invariant we have is:
SI 0: All components can only perform their designated
functions.
For example, the UI subsystem can never ask for
cookie data or the storage manager cannot impersonate
a network process to send synthesized attack HTTP data
to a web page instance.
4.5 Driver invariants
The two driver invariants the IBOS kernel enforces are:
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
SI 1: Drivers cannot access DMA buffers directly.
SI 2: Devices can only access validated DMA buffers.
In our approach, we use a split driver architecture
where we separate the management of device control reg-
isters from the use of device buffers (SI 1). For example,
our Ethernet driver never has access to transmit or re-
ceive buffers directly. Instead, it knows the physical ad-
dresses where the IBOS kernel stores these buffers, and
it programs the NIC to use them. By separating these
two functions we can interpose on the communications
between them to ensure that IBOS upholds browser secu-
rity policies, even if an attacker completely compromises
a shared driver.
Using this split architecture, processes fill in device-
specific buffers for DMA transfers, and the IBOS ker-
nel infers when drivers initiate DMA transfers to ensure
that the driver instructs the device to use a verified DMA
buffer (SI 2). Fortunately, DMA buffers tend to use
well-defined interfaces, like Ethernet frames for Ether-
net drivers, so the IBOS kernel can readily glean security
relevant information from these DMA buffers before the
device accesses them. Unfortunately, the interface be-
tween drivers and devices is device-specific, so the IBOS
kernel must have a small state machine for each device
to properly infer DMA transfers. However, we found this
state machine to be quite small for the devices that we use
in IBOS.
In IBOS we implement a driver for the e1000 NIC, a
VESA BIOS Extensions driver for our video card, and
drivers for the mouse and keyboard.
4.6 Storage invariants
The primary invariant we strive to enforce in the storage
manager is:
SI 3: All of our key-value pairs maintain confidentiality
and integrity even if the storage stack itself becomes
compromised.
To enforce this invariant, our IBOS kernel encrypts
all objects before passing them to the storage subsystem.
To encrypt data, the IBOS kernel maintains separate en-
cryption keys for all of the namespaces on the IBOS sys-
tem. These namespaces include separate namespaces for
HTTP cookies based on the domain of the cookie, sep-
arate namespaces for web page instances based on the
origin of the page, separate namespaces for each of our
subsystems, and a separate namespace for all traditional
apps. When the IBOS kernel passes a request to the stor-
age manager it will append the security labels, a copy
of the key from the key-value pair, and a hash of the
contents to the payload before encrypting the data and
USENIX Association
USENIX Association
Passing it to the storage subsystem. When the IBOS ker-
nel retrieves this data, it can decrypt the data and check
the labels and integrity of the information. By using en-
cryption, the IBOS kernel does not need to implement
security invariants for any of our storage drivers, and our
storage subsystem is free to make data persistent using
any mechanisms it sees fit, such as the network (like in
our implementation) or via a disk-based storage system.
Our current implementation does not make any efforts
to avoid an attacker that deletes objects or replays old
storage data. For web applications this limitation has
only a small effect because the cookie standards do not
require browsers to keep cookies persistently and be-
cause web applications often limit the lifetime of cookies
using expiration dates, which are also part of the cookie
standard. However, if this limitation did become prob-
lematic, we could apply the principles learned from dis-
tributed or secure file systems to provide stronger guar-
antees.
4.7 Network process invariants
Our IBOS kernel maintains five main invariants for net-
work processes:
S14: The kernel must route network requests from web
page instances to the proper network process.
SI5: The kernel must route Ethernet frames from the
NIC to the proper network processes.
S16: Ethernet frames from network processes to the
NIC must have an IP address and TCP port that
matches the origin of the network process.
SI7: HTTP data from network processes to web page
instances must adhere to the SOP.
SI 8: Network processes for different web page in-
stances must remain isolated.
To help enforce these invariants, IBOS puts all net-
work processes in their own protection domains. If a web
page instance makes a HTTP request, the kernel will ex-
tract the origin from the request message and either route
this request to an existing network process that has the
same label, or it will create a new network process and
label the network process with the origin of the HTTP
request. Likewise, the kernel inspects incoming Ether-
net frames to extract the origin and TCP port informa-
tion, and routes these frames to the appropriately labeled
network process. By putting network processes in their
own protection domains, the kernel naturally ensures that
network requests from web page instances and Ethernet
frames from the NIC are routed to the correct network
process (SI 4) (SI 5).
To ensure that the NIC sends outgoing Ethernet frames
to the correct host, the IBOS kernel checks all outgoing
Ethernet frames before sending them to the NIC to check
the IP address and TCP port against the label of the send-
ing network process (SI 6). Also, the IBOS kernel checks
cookies before passing them to the network process to
ensure that all of the origin labels adhere to cookie stan-
dards. By performing these checks, the IBOS kernel en-
sures that the NIC sends outgoing network requests to
the proper host and that the request can only include data
that would be available to the server anyway.
To enforce the SOP, the IBOS kernel inspects HTTP
data before forwarding it to the appropriate web page
instance and drops any HTML documents from differ-
ent origins (SI 7). To inspect data, the kernel uses the
content sniffing algorithm from Chrome [10] to identify
HTML documents so the kernel can check to make sure
that the origin of HTML documents and the origin of the
web page instance match. This countermeasure prevents
compromised web page instances from peering into the
contents of a cross-origin HTML document, thus pre-
venting the compromised web page instance from read-
ing sensitive information included in the HTML docu-
ment.
To help isolate web page instances from each other,
we also label network processes with the origin of the
web page instance (SI 8). This second label is used only
for network access control decisions and does not affect
the cookie policy, which is predicated on the origin of
the network request. To access network processes, the
origin of the web page instance must match the origin of
this second label. By using this second label, the IBOS
kernel isolates network requests from different web page
instances to the same origin. As a result of this isolation,
a web page instance that is served a malicious network
resource (e.g., a malicious ad [41]) that compromises a
network process remains isolated from other web page
instances. If an attacker can compromise a network pro-
cess, IBOS limits the damage to the web page instance
that included the malicious content.
4.8 Ul invariants
The three UI invariants that the IBOS kernel enforces are:
SI9: The browser chrome and web page content dis-
plays are isolated.
SI10: Only the current tab can access the screen,
mouse, and keyboard.
SI11: The URL of the current tab is displayed to the
user.
The key mechanisms that our UI subsystem uses to
provide isolation are to use a frame buffer video driver
and page protections to isolate portions of the screen (SI
9). Our video driver uses a section of memory, called
a frame buffer, for writing to the screen. Processes
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) 23
24
Kernel
UL
Rar OPEN CIRRUS; = Se
Aleriitess A GLOBAL CLOUD <¥"™""
COMPUTING facebook
7 TESTBED See
‘ uiue.edu
rh Pe a...
Web Page Susan
Instance ii _ Mn |
Page
Protection
Figure 3: IBOS display isolation. This figure shows how
IBOS divides the display into three main parts: a bar at
the top for the kernel, a bar for browser chrome, and the
rest for displaying web page content. The IBOS kernel
enforces this isolation using page protections and without
relying on a window manager.
write pixel values to this frame buffer and the graph-
ics card displays these pixels. Although our mechanism
makes heavy use of the software rastering available in Qt
Framework[3], our experiences and anecdotal evidence
from the Qt developers shows that software rastering can
perform roughly as fast as native X drivers running on
Linux [7]. The key advantage of our approach is that
the IBOS kernel can use standard page-protection mech-
anisms to isolate portions of the screen. Although our
current implementation does not support hardware accel-
eration, we believe that our techniques will work because
the IBOS kernel can interpose on standardized accelera-
tion hardware/software interfaces, such as OpenGL and
DirectX.
To provide screen isolation, we divide up the screen
into three horizontal portions (Figure 3). At the top, we
reserve a small bar that only the IBOS kernel can access.
We use the next section of the screen for the UI subsys-
tem to draw the browser chrome. Finally, we provide
the remainder of the screen to the web page instance. To
ensure that only one web page instance can write to the
screen at any given time, we only map the frame buffer
memory region into the currently active web page in-
stance and we only route mouse and keyboard events to
this currently active web page instance (SI 10).
To switch tabs, the UI subsystem notifies the IBOS
kernel about which tab is the current tab, and the IBOS
kernel updates the frame buffer page table entries ap-
propriately. However, a malicious UI manager could
switch tabs arbitrarily and cause the address bar and the
tab content to become out of sync (e.g., shows a page
from attacker.com, but claims the page comes from
uiuc.edu). One alternative we considered for this UI
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
inconsistency was interposing on mouse and keyboard
clicks to infer which tab the user clicked on, and also
performing optical character recognition on the address
bar to determine the address that the UI manager is dis-
playing. However, tracking this level of detail would re-
quire far too much implementation specific information
and would require the IBOS kernel to track additional
events like a user switching the order of tabs.
Our approach for the IBOS kernel is to use the kernel
display area to display the URL for the currently visi-
ble web page instance (SI 11). The kernel derives the
URL from the label of the currently visible web page
instance, providing high assurance that the URL the ker-
nel displays matches the URL of the visible web page
instance without tracking implementation specific states
and events in the UI manager. Although this security in-
variant appears simple, it is something that modern web
browsers have had trouble getting right [13].
4.9 Web page instances and iframes
The IBOS kernel creates a new web page instance each
time a user clicks on a link or types a new URL in the
address bar. To enforce the SOP on iframes, we run
cross-origin iframes in separate web page instances.
This separation allows us to fully track the SOP using
kernel visible entities. To facilitate communication be-
tween web page instances and the iframes that they
host, we marshal postMessage calls between the two.
Our current display isolation primitives are coarse
grained and we rely on the web page instance to manage
cross-origin iframe displays even though iframes
run in separate protection domains. However, current
display policies allow web page instances to draw over
cross-origin iframes that they host, so this design deci-
sion has no impact on current browser policies. One po-
tential shortcoming of this display management approach
is that compromised web page instances can read the dis-
play data for embedded iframes. Fortunately, many
sites with sensitive information, like facebook.com
and gmail.com, use frame busting techniques [34] to
prevent cross-origin sites from embedding them, which
the IBOS kernel can enforce.
4.10 Custom policies
Our main focus of this project is being able to enforce
current browser policies from the lowest layer of soft-
ware. However, we also want to create an architecture
that exposes enough browser states and events to en-
able novel browser security policies. Attacks such as
XSS operate within traditional browser policies and can
be difficult to prevent without relying on the HTML or
JavaScript engine implementations. Although our archi-
USENIX Association
USENIX Association
tecture cannot prevent XSS, our goal is to prevent these
types of attacks from causing damage.
One mechanism we implement in IBOS is to give
a web server the ability to create its own more re-
strictive security policy to prevent attacks from sending
sensitive information to third-party hosts. In our cus-
tom policy, we allow web sites to specify a server-side
policy file that IBOS retrieves to restrict network ac-
cesses for a web page instance, similar to Tahoma man-
ifests [15]. For example, assume that a bank website
located at http: //www.bank.com creates a policy
file at http: //www.bank.com/.policy that spec-
ifies the online bank system can only access resources
from www. bank.comor data.bank.com. IBOS re-
trieves the policy file and automatically applies a more
restrictive policy for the online bank web application.
This restrictive policy prevents an attacker from sending
stolen information to a third-party host, providing an ad-
ditional layer of protection for the web application.
5 Implementation
The implementation of IBOS is divided into three parts:
the IBOS kernel, IBOS messaging passing interfaces,
and IBOS subsystems. The IBOS kernel is implemented
on top of the L4Ka::Pistachio microkernel and runs on
X86-64 uniprocessor and SMP platforms. We modi-
fied L4Ka to improve its support for SMP systems. The
IBOS kernel schedules processes based on a static prior-
ity scheduling algorithm.
The IBOS kernel provides three basic APIs (i.e.,
send(), recv(), and poll ()) to facilitate message
passing. Applications use send() and recv() for
communication and call poll () to wait for new mes-
sages. The IBOS kernel intercepts all messages and au-
tomatically extracts the semantics from them, like cre-
ating a new web page instance or forwarding cookies to
network processes. Then the kernel inspects the seman-
tics to make sure they conform to all security invariants
and policies that we described in previous sections.
The IBOS subsystems implements APIs for web
browsers and traditional applications. They are built on
top of an IBOS-specific uClibe [6] C library, lwIP [17]
TCP/IP stack and the Qt Framework [3]. The web
browser also uses an IBOS-specific WebKit [5] to parse
and render web pages.
To support traditional apps, we use our uClibc and Qt
implementations to provide access to browser abstrac-
tions using the UNIX-like abstractions of the C runtime,
and GUI support from Qt. We use a few Qt sample pro-
grams for testing and we implement one plugin. Our plu-
gin is a PDF viewer that uses the Ghostscript PDF ren-
dering engine with bindings for Qt.
System LOC
IBOS 42,044
IBOS Kernel 8,905
L4Ka::Pistachio 33,139
Firefox on Linux > 5,684,639
Firefox 3.5 2,171,267
GTK+ 2.18 489,502
glibc 2.11 740,314
X.Org 7.5 653,276
Linux kernel 2.6.31 1,630,280
ChromeOS > 4,407,066
Chrome browser kernel 4.1.249 714,348
GTK+ 2.18 489,502
glibc 2.11 740,314
ChromeOS kernel & services (May 2010) 2,462,902
Table 1: Estimation of LOC of TCBs for IBOS, Firefox
on Linux, and ChromeOS. LOC counts are also shown
for some major components that are included in the TCB.
6 Evaluation
This section describes our evaluation of IBOS. In our
evaluation, we analyze the security of IBOS by measur-
ing the number of lines of code (LOC) in the IBOS TCB
and comparing it with other systems, and by looking at
recent bugs in comparable systems and counting vulner-
abilities that IBOS is susceptible to. We also revisit the
example attacks we discussed in the introduction, and we
measure the performance.
6.1 TCB
In IBOS, our goal is to minimize the TCB for web
browsers and to simplify browser-based systems. To
quantitatively evaluate our effort, we count the LOC in
the IBOS TCB and compare it against the TCB for Fire-
fox and ChromeOS. IBOS supports fewer hardware ar-
chitectures, platforms, device drivers and features, such
as browser extensions, than Firefox running on Linux
and ChromeOS. For a fair comparison, we only count
source code that is used for running above Linux and on
the X86-64 platform. Also, we omit all device drivers
from our counts except for the drivers we implement in
IBOS.
Table 1 shows the result of LOC counts in the TCB for
these three systems, measured by SLOCCount [54]. For
Firefox and ChromeOS, our counts are conservative be-
cause we only count the major components that make up
the TCB for each system — there are likely more compo-
nent that are also in the TCB for these systems. Because
the IBOS TCB has only around 42K LOC, it is possible
to formally verify or manually review the entire IBOS
TCB. And in fact, one L4 type microkernel has already
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) 25
26
Affected Component || Num. | Prevented
Linux kernel overall 21 20 (95%)
File system 12 12 (100%)
Network stack > 5 (100%)
Other 4 3 (75%)
X Server 2 2 (100%)
GTK+ & glibc 5 5 (100%)
Overall 28 27 (96 %)
Table 2: OS and library vulnerabilities. This table shows
the number of vulnerabilities that IBOS prevents.
been formally verified [32].
6.2 OS and library vulnerabilities
To evaluate the security impact of IBOS’s reduced TCB,
we obtained a list of 74 vulnerabilities found in the Linux
kernel, X Server, GTK+, and glibc this year so far (as
of Sep. 18, 2010) [1] to see how the IBOS architecture
handles them. Out of the 74 vulnerabilities, 20 are re-
lated to unsupported hardware architectures and devices,
and 26 cause denial-of-service, which is out-of-scope for
this paper. For the remaining 28, we classify them based
on the subsystem the vulnerability lies in to determine if
IBOS is susceptible to these vulnerabilities.
Table 2 shows IBOS is able to prevent 27 of 28 vul-
nerabilities (96%). The only vulnerability we miss is
a memory corruption vulnerability in the e1000 Ether-
net driver. Normally IBOS is not susceptible to bugs in
device drivers, but this particular bug resulted from the
driver not accounting properly for Ethernet frames larger
than 1500 bytes, and this type of logic is what our NIC
verification state machine uses, so we counted this bug
against IBOS.
6.3. Browser vulnerabilities
To evaluate security improvements that IBOS makes
for browsers themselves, we compared how well
IBOS could contain or prevent vulnerabilities found in
Google’s Chrome browser. For this evaluation, we ob-
tained a list of 295 publicly visible bugs with the “se-
curity” label in Chrome’s bug tracker. Out of the 295
bugs, 42 cause denial-of-service such as a simple crash or
100% CPU utilization. IBOS does not address denial-of-
service or resource management currently. An additional
78 are either invalid, duplicate, not actually security is-
sues, or related to features that IBOS does not have, such
as browser extensions. For the remaining 175 bugs, we
examined each of them to the best of our knowledge and
classified them into the following seven categories and
compared how Chrome and IBOS handle those cases:
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
Memory exploitation: an attacker could use a memory
corruption bug to deploy a remote code execution attack.
For Chrome, if the bug is in its rendering engine, Chrome
contains the attack. However, bugs in the browser kernel
give attackers access to the entire browser. For IBOS,
bugs in either the rendering engine or other service com-
ponents are contained as they are all out of the TCB.
XSS: browsers rely on careful sanitization and correct
processing of different encodings to prevent XSS attacks.
For both Chrome and IBOS, it is infeasible to eliminate
XSS attacks, but they both contain the attacks in the af-
fected web apps.
SOP circumvention: Chrome runs contents in frames
from different origins in a single address space and uses
scattered “if” and “else” statements to enforce the same-
origin policy. This logic can be sometime subverted. In
IBOS, we run iframes in different web page instances to
provide strong isolation and check cross-origin access in
the IBOS kernel.
Sandbox bypassing: Chrome uses sandboxing tech-
niques, such as SELinux, to limit the rendering engine’s
authority. However, rule-based sandboxing is complex
and can be bypassed in some scenarios. In IBOS, we
designed browser abstractions to restrict the authority of
each subsystem, which are immune to this kind of prob-
lem naturally.
Interface spoofing: browsers are sometime vulnerable
to visual attacks in which a malicious website can use
complex HTTP redirection or even replicate the “look
and feel” of victim websites to deploy phishing. Chrome
uses a blacklist-based filter to warn users of malicious
websites. In IBOS, the IBOS kernel separates the dis-
play of different web page instances and uses the labels
of web page instances to display the correct URL in the
top of the screen to give the user a visual cue of which
website he or she is visiting.
UI design flaw: some security concerns arise because
of careless implementation, such as showing users’ pass-
words in plain text. Both Chrome and IBOS are vulnera-
ble to this type of problem.
Misc: some vulnerabilities could not easily be classi-
fied and mostly have low security severity. This is the
category for those remaining bugs.
In Table 3, we show the detailed results of the analysis
of the 175 vulnerabilities, broken down by the classifi-
cations above. We examined each of them to determine
whether Chrome contains the threats in the affected com-
ponents, and whether IBOS contains or eliminates the at-
tacks. The table shows IBOS successfully protects users
from 135 of the 175 vulnerabilities (77%).
The largest portion of bugs are browser implementa-
tion flaws that cause memory corruption and allow re-
mote code execution. Chrome does a fairly good job
containing most of them when they are in the rendering
USENIX Association
USENIX Association
Chrome IBOS
Category Example Num. | Contained | Contained or eliminated
Memory exploitation || A bug in layout engine leads to remote code execution 82 71 (86%) 79 (96%)
XSS XSS issue due to the lack of support for ISO-2022-KR 14 12 (87%) 14 (100%)
SOP circumvention XMLHttpRequest allows loading from another origin 21 0 (0%) 21 (100%)
Sandbox bypassing Sandbox bypassing due to directory traversal 12 0 (0%) 12 (100%)
Interface spoofing Two pages merge together in certain situation 6 0 (0%) 6 (100%)
Ul design flaw Plain-text information leak due to autosuggest 17 0 (0%) 0 (0%)
Misc Geolocation events fire after document deletion 22 0 (0%) 3 (14%)
Overall 175 83 (46%) 135 (77%)
Table 3: Browser vulnerabilities. This table shows the number of Chrome vulnerabilities that Chrome itself contains
and IBOS contains or eliminates.
engine. However, Chrome is unable to contain exploits
in the browser kernel. A good example is a bug in the
HTTP chunked encoding module in the browser kernel,
which opens the possibility for a remote attacker to inject
code. In IBOS, the TCP/IP and HTTP stack is pushed out
of the TCB, and is replicated and isolated according to
browser security policies. Thus, IBOS is able to contain
this bug. The three memory corruption bugs IBOS could
not contain were from bugs in Chrome’s message pass-
ing system. Because the IBOS message passing logic
resides within our TCB, we counted these bugs as bugs
that IBOS would have missed.
6.4 Motivation revisited
In the introduction, we listed some examples of attacks
that an attacker can use to still cause damage to modern
secure web browsers by exploiting code in their TCB.
We revisit these examples again to argue that IBOS can
prevent them.
A compromised Ethernet driver cannot access the
DMA buffers used by the device. Even if an attacker
exploits the Ethernet driver, he or she still cannot tamper
with network packets because the driver does not have
access to DMA buffers and because the IBOS kernel val-
idates all transmit and receive buffers that the driver sets.
A compromised storage module has little impact on
data confidentiality and integrity. The IBOS kernel en-
crypts all data with secret keys that only the IBOS ker-
nel has access to. Stored objects are tagged with a hash
and origin information so that the IBOS kernel is able
to detect tampered data. The only thing a compromised
storage module can do is delete objects.
A compromised network stack is constrained as well.
In IBOS, every network process runs a complete net-
work stack. A compromised network process cannot
send users’ data to a third party host as the IBOS ker-
nel ensures it can only communicate with the expected
host. Network processes do have the ability to modify or
replay HTTP requests, but the web server might have a
mechanism to defend against replay attacks.
A Compromised window manager cannot affect other
subsystems in IBOS. In IBOS, the role of window man-
ager is simplified to only draw the browser chrome. It
can change some potentially sensitive information, such
web page titles. However, the IBOS kernel displays the
URL of the current tab in the kernel display area, provid-
ing users with some visual cues as to the provenance of
the displayed web content.
6.5 Performance
To evaluate the performance implication of IBOS’s ar-
chitecture, we compare its browsing experience to other
web browsers running in Linux. All experiments were
carried out on a 2.33GHz Intel Core 2 Quad CPU
Q8200 with 4GB of memory, a 320GB 7200RPM Sea-
gate ST3320613 SATA hard drive and an Intel PRO/1000
NIC connected to 1000 Mbps Ethernet. For Linux, we
used Ubuntu 9.10 with kernel version 2.6.3 1-16-generic
(x86-64).
We use page load latency to represent browsing ex-
perience. Page load latency is defined as the elapsed
time between initial URL request and the DOM onload
event. We compare IBOS with Firefox 3.5.9, Chrome
for Linux 4.1.249. We also ported most of the IBOS
browser components to Linux platform (noted as IBOS-
Linux) to focus on the performance impact of our IBOS
kernel architecture. In IBOS, we statically allocate pro-
cessors for subsystems as follows: the kernel and device
drivers run on CPUO, network processes run on CPU1,
web page instances run on CPU2, and all other compo-
nents run on CPU3. IBOS, IBOS-Linux, and Chrome all
use a same version of WebKit from February 2010 with
just-in-time JavaScript compilation and HTTP pipelining
enabled. For the WebKit-based browsers, we instrument
them to measure the time in between the initial URL re-
quest and the DOM onload event. For Firefox, we use
an extension that measures these same events. To reduce
noise introduced by our network connection, we load
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) 27
28
T T T T
3,000 |- J IBOS 7
| IBOS-Linux
5500 a Firefox
. BY [Es] Chrome
SS
Sah
2,000 PEs
poe
Red
Beets
1,500 fo
» RF
Rd
Re
e
1,000 pF =
be cH
< Be RZEN SS
500 SEF = (4 Set
Ro = i REA if Ret
es + big ka he
tee Sy Re Re hed Red
mE ht Re Re beg Req
Google Maps _ Bing Craigslist CS@Illinois Wikipedia Faceboo
Figure 4: Page load latencies for IBOS and other web
browsers. All latencies are shown in milliseconds.
each web site using a fresh web page/browser instance
with an empty cache 15 times and report the average of
the five shortest page load latency times.
In Figure 4, we present the page load latency times
for six popular websites and show the standard devia-
tions with the error bars. Overall, Chrome has the short-
est page load latencies due to its effective optimization
techniques. For maps.google.com, IBOS, IBOS-
Linux, and Chrome out-perform Firefox, possibly due
to optimization in the WebKit engine for this particular
site. For www.bing.com, sfbay.craigslist.
org and cs.illinois.edu, IBOS, IBOS-Linux,
and Firefox show roughly the same results. IBOS has the
fastest loading time for craigslist. Craigslist
is a simple web site with few HTTP requests and with a
large number of HTML elements. We hypothesize that
the small performance improvement is due to the simpli-
fied IBOS software stack.
Both en.wikipedia.org/wiki/Main_Page
and www. facebook.com have more HTTP requests
than any of the other sites, and we observe slower page
load latencies for IBOS than for other browsers. For
these experiments IBOS performs slower than IBOS-
Linux. Because we use the IBOS components in Linux,
we believe that this performance difference occurs from
overhead in the IBOS kernel. To test this hypothesis, we
ran a number of micro benchmarks on the two systems
and we believe that the overhead is due to contention for
spinlocks in the L4 IPC implementation. The net effect
of this contention is that heavy use of network processes
requires heavy use of IPC, which adds latency to all IPC
messages and slows down the overall system. However,
the IBOS-Linux results for these experiments show that
this slow down is not fundamental and can be fixed with
a more mature kernel implementation.
Overall, the page load latency experiments show that
even with a prototype implementation of IBOS, our ar-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
chitecture will not slow down the browsing speed signif-
icantly for the web sites we tested.
7 Additional related work
7.1 Alternative kernel architectures
Operating systems designed to reduce the trusted com-
puting base for applications are not new. For example,
several recent OSes propose using information flow to
allow applications to specify information flow policies
that are enforced by a thin kernel [18, 57, 33]; KeyKOS
[12], EROS [45], and seL4 [32] provide capability sup-
port using a small kernel; and Microkernels [24, 27, 28]
push typical OS components into user space. In IBOS,
we apply these principles to a new application — the web
browser — and include support for user interface com-
ponents and window manager operations. Also, these
previous approaches support general purpose security
mechanisms, like information flow and capabilities, and
shared resources and device drivers are part of the TCB.
The IBOS security policy is specific to web browsers,
and although this is less general, we can track this pol-
icy to hardware abstractions and can remove drivers and
other shared components from our TCB.
Both Exokernels [19, 31] and L4 [27] rethink low-
layer software abstractions. In both projects, they ad-
vocate exposing abstractions that are close to the under-
lying hardware to enable applications to customize for
improved performance. In IBOS we build on these pre-
vious works — in fact we use the L4Ka::Pistachio L4 [8]
MMU abstractions and message passing implementation
directly. However, the key difference between our work
and L4 and Exokernel is that we expose high-level ap-
plication abstractions at our lowest layer of software, not
low-level hardware abstractions. Our focus is on making
web browsers more secure and the system software we
use to accomplish this improved security.
7.2 Browser security
A number of recent papers have proposed new browser
architectures including SubOS [29, 30], safe web pro-
grams [44], OP [26], Chrome [11, 43], Gazelle [52], and
ServiceOS [38]. Although the browser portion of IBOS
does resemble some of these works, they all run on top of
commodity OSes and include complex libraries and win-
dow managers in their TCB, something that IBOS avoids
by focusing on the OS architecture of our system.
The webOS from Palm [40] and the upcoming
ChromeOS from Google [25] run a web browser on top
of a Linux kernel. ChromeOS includes kernel harden-
ing using trusted boot, mandatory access controls, and
sandboxing mechanisms for reducing the attack surface
USENIX Association
USENIX Association
of their system. However, ChromeOS and IBOS have
fundamentally different design philosophies. ChromeOS
starts with a large and complex system and tries to re-
move and restrict the unused and unneeded portions of
the system. In contrast, IBOS starts with a clean slate
and only adds to our system functionality needed for
our browser. Although our approach does require im-
plementing from scratch low-level software and fitting
device drivers to a new driver model, the end result has 2
to 3 orders of magnitude fewer lines of code in the TCB,
while still retaining nearly all of the same functionality.
In the Tahoma browser [15], the authors propose using
virtual machine monitors (VMMs) to enable web appli-
cations to specify code that runs on the client. Tahoma
uses server-side manifests to specify the security pol-
icy for the downloaded code and the VMM enforces
this security policy. Tahoma does expose a few browser
abstractions from their VMM to help manage UI ele-
ments and network connections, but operates mostly on
hardware-level abstractions. Because Tahoma operates
on hardware-level abstractions, Tahoma is unable to pro-
vide full backwards-compatible web semantics from the
VMM and more fine-grained protection for browsers,
such as isolating i frames embedded in a web applica-
tion. Also, many modern VMMs use a full-blown com-
modity OS in a privileged virtual machine or host OS for
driver support, leaving tens of millions of lines of code
in the TCB potentially.
7.3 Device driver security
Device driver security has focused on three main topics.
First, several projects focus on restricting driver access to
I/O ports and device access to main memory via DMA.
For example, RVM uses a software-only approach to re-
strict DMA access of devices [55], SVA prevents the OS
from accessing driver registers via memory mapped I/O
through memory safety checks [16], and Mungi [35] re-
lies on using a hardware IOMMU to limit which mem-
ory regions are accessible from devices. Second, sys-
tem designers isolate drivers from the rest of the system.
This isolation can be achieved by running drivers in user-
mode, which has been a staple of Microkernel systems
[24, 36, 28], using software to protect the OS from ker-
nel drivers [20, 58], or by using page table protections
within the OS [49, 48]. The driver security architec-
ture in IBOS differs from these approaches because our
system provides fine-grained protection for individual re-
quests within a shared driver in addition to isolating the
driver from the rest of the system.
7.4 Secure window managers
A number of recent projects have looked at reducing the
TCB for window managers. For example DoPE [21] and
Nitpicker [22] move widget rendering from the server
to the client, leaving the server to only manage shared
buffers. CMW [56], EWS [46], and TrustGraph [39] also
use clients for rendering, but are able to apply capabili-
ties and mandatory access control policies to application
user-interface elements. In IBOS, we deprecate the gen-
eral window notion of modern computer systems in favor
of the simpler browser chrome and tab motif, allowing
us to track our security policies down to the underlying
graphics hardware on our system.
8 Conclusions
In this paper, we presented IBOS, an operating system
and web browser co-designed to reduce drastically the
trusted computing base for web browsers and to sim-
plify browsing systems. To achieve this improvement,
we built IBOS with browser abstractions as first-class OS
abstractions and removed traditional shared system com-
ponents and services from its TCB. With our new archi-
tecture, we showed that IBOS enforced traditional and
novel security policies, and we argued that the overall
system security and usability could withstand successful
attacks on device drivers, browser components, or tradi-
tional applications. Our experimental results showed that
IBOS added little overhead when compared to today’s
high-performance browsers running on fast and mature
commodity operating systems.
Acknowledgment
We would like to thank Brad Chen, Steve Gribble, and
Hank Levy for their feedback on our security analy-
sis. We would also like to thank our shepherd Nickolai
Zeldovich, Anthony Cozzie, and Matt Hicks who pro-
vided valuable feedback on this paper. This research
was funded in part by NSF grants CNS 0834738 and
CNS 0831212, grant NO014-09-1-0743 from the Office
of Naval Research, AFOSR MURI grant FA9550-09-01-
0539, and by a grant from the Internet Services Research
Center (ISRC) of Microsoft Research.
References
[1] CVE - Common Vulnerabilities and Exposures (CVE). http:
//cve.mitre.org.
[2
far}
Gecko plugin API reference. https://developer.
mozilla.org/en/Gecko_Plugin_API_Reference.
[3] Qt - A Cross-platform application and UI.
nokia.com/.
http://qt.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) 29
30
[10
[11]
[12]
[13]
[14]
[15]
[16]
[17]
[18]
[19]
[20]
Symantec internet security threat report april 2010.
http://www. symantec.com/business/theme.
jsp?themeid=threatreport.
The WebKit Open Source Project. http: //webkit.org/.
uClibe. http: //www.uclibc.org/.
Qt labs blogs: So long and thanks for the blit, 2008.
http://labs.trolltech.com/blogs/2008/10/
22/so-long-and-thanks-for-the-blit/.
L4Ka::Pistachio microkernel, 2010.
projects/pistachio.
http://14ka.org/
ANDERSON, J. P. Computer security technology planning study.
Tech. rep., HQ Electronic Systems Division (AFSC), October
1972. ESD-TR-73-51.
BARTH, A., CABALLERO, J., AND SONG, D. Secure content
sniffing for web browsers or how to stop papers from reviewing
themselves. In Proceedings of the IEEE Symposium on Security
and Privacy (May 2009).
BARTH, A., JACKSON, C., REIS, C., AND THE
GOOGLE CHROME TEAM. The security archi-
tecture of the chromium browser, 2008. http:
//crypto.stanford.edu/websec/chromium/
chromium-security-architecture.pdf.
BOMBERGER, A. C., FRANTZ, W. S., HARDY, A. C., HARDY,
N., LANDAU, C. R., AND SHAPIRO, J. S. The KeyKOS nanok-
ernel architecture. In Proceedings of the Workshop on Micro-
kernels and Other Kernel Architectures (Berkeley, CA, USA,
1992), USENIX Association, pp. 95-112.
CHEN, S., MESEGUER, J., SASSE, R., WANG, H. J., AND
WANG, Y.-M. A systematic approach to uncover security flaws
in GUI logic. In Proceedings of the 2007 IEEE Symposium on
Security and Privacy (May 2007), pp. 71-85.
CHEN, S., Ross, D., AND WANG, Y.-M. An analysis of
browser domain-isolation bugs and a light-weight transparent de-
fense mechanism. In Proceedings of the 14th ACM Conference on
Computer and Communications Security (CCS) (2007), pp. 2-11.
Cox, R. S., HANSEN, J. G., GRIBBLE, S. D., AND LEvy,
H. M. A safety-oriented platform for web applications. In Pro-
ceedings of the 2006 IEEE Symposium on Security and Privacy
(May 2006), pp. 350-364.
CRISWELL, J., GEOFFRAY, N., AND ADVE, V. Memory safety
for low-level software/hardware interactions. In Proceedings of
the Eighteenth Usenix Security Symposium (August 2009).
DUNKELS, A., WOESTENBERG, L., MANSLEY,
AND MONOSES, J. lwIP embedded TCP/IP
http://savannah.nongnu.org/projects/lwip/, 2004.
K.,
stack.
EFSTATHOPOULOS, P., KROHN, M., VANDEBOGART, S.,
FREY, C., ZIEGLER, D., KOHLER, E., MAZIERES, D.,
KAASHOEK, F., AND MORRIS, R. Labels and event processes
in the asbestos operating system. In SOSP ’05: Proceedings of
the Twentieth ACM Symposium on Operating Systems Principles
(New York, NY, USA, 2005), ACM, pp. 17-30.
ENGLER, D. R., KAASHOEK, M. F., AND JR., J. O. Exok-
ernel: an operating system architecture for application-level re-
source management. In Proceedings of the 1995 Symposium on
Operating Systems Principles (December 1995), pp. 251-266.
ERLINGSSON, U., ABADI, M., VRABLE, M., BUDIU, M., AND
NECULA, G. C. Xfi: software guards for system address spaces.
In OSDI ’06: Proceedings of the 7th symposium on Operating
systems design and implementation (Berkeley, CA, USA, 2006),
USENIX Association, pp. 75-88.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
[21]
[22]
[23]
[24]
[25]
[26]
[27]
[28]
[29]
[30]
[31]
[32]
[33]
[34]
[35]
[36]
FESKE, N., AND HARTIG, H. DOpE - a window server for real-
time and embedded systems. In RTSS ’03: Proceedings of the
24th IEEE International Real-Time Systems Symposium (Wash-
ington, DC, USA, 2003), IEEE Computer Society, p. 74.
FESKE, N., AND HELMUTH, C. A Nitpicker’s guide to a
minimal-complexity secure GUI. In ACSAC ’05: Proceedings
of the 21st Annual Computer Security Applications Conference
(Washington, DC, USA, 2005), IEEE Computer Society, pp. 85—
94.
GARFINKEL, T. Traps and Pitfalls: Practical Problems in Sys-
tem Call Interposition Based Security Tools. In Proceedings of
the 2003 Network and Distributed System Security Symposium
(NDSS) (February 2003).
GOLUB, D., DEAN, R., FORIN, A., AND RASHID, R. Unix
as an Application Program. In Proceedings of the 1990 USENIX
Summer Conference (1990).
GOOGLE INC. Chromium OS, 2010.
chromium.org/chromium-os.
http://www.
GRIER, C., TANG, S., AND KING, S. T. Secure web brows-
ing with the OP web browser. In Proceedings of the 2008 IEEE
Symposium on Security and Privacy (May 2008), pp. 402-416.
HARTIG, H., HOHMUTH, M., LIEDTKE, J., WOLTER, J., AND
SCHONBERG, S. The performance of ju-kernel-based systems. In
SOSP ’97: Proceedings of the sixteenth ACM Symposium on Op-
erating Systems Principles (New York, NY, USA, 1997), ACM,
pp. 66-77.
HERDER, J. N., BOs, H., GRAS, B., HOMBURG, P., AND
TANENBAUM, A. S. MINIX 3: a highly reliable, self-repairing
operating system. SIGOPS Oper. Syst. Rev. 40, 3 (2006), 80-89.
IOANNIDIS, S., AND BELLOVIN, S. M. Building a secure web
browser. In Proceedings of the FREENIX Track: 2001 USENIX
Annual Technical Conference (June 2001).
IOANNIDIS, S., BELLOVIN, S. M., AND SMITH, J. Sub-
operating systems: A new approach to application security. In
SIGOPS European Workshop (September 2002).
KAASHOEK, M. F., ENGLER, D. R., GANGER, G. R.,
BRICENO, H. M., HUNT, R., MAZIERES, D., PINCKNEY, T.,
GRIMM, R., JANNOTTI, J.. AND MACKENZIE, K. Application
performance and flexibility on exokernel systems. In SOSP ’97:
Proceedings of the sixteenth ACM symposium on Operating sys-
tems principles (New York, NY, USA, 1997), ACM, pp. 52-65.
KLEIN, G., ELPHINSTONE, K., HEISER, G., ANDRONICK, J.,
Cock, D., DERRIN, P., ELKADUWE, D., ENGELHARDT, K.,
KOLANSKI, R., NORRISH, M., SEWELL, T., TUCH, H., AND
WINWOOD, S. seL4: formal verification of an os kernel. In
SOSP ’09: Proceedings of the ACM SIGOPS 22nd symposium
on Operating systems principles (New York, NY, USA, 2009),
ACM, pp. 207-220.
KROHN, M., Yip, A., BRODSKY, M., CLIFFER, N.,
KAASHOEK, M. F., KOHLER, E., AND MORRIS, R. Informa-
tion flow control for standard OS abstractions. In SOSP ’07: Pro-
ceedings of twenty-first ACM Symposium on Operating Systems
Principles (New York, NY, USA, 2007), ACM, pp. 321-334.
LAWRENCE, E. Combating clickjacking with x-
frame-options, March 2010. http://blogs.msdn.
com/b/ieinternals/archive/2010/03/30/
combating-clickjacking-with-—x-frame-options.
aspx.
LESLIE, B., AND HEISER, G. Towards untrusted device drivers.
Tech. rep., UNSW-CSE-TR-0303, 2003.
LEVASSEUR, J., UHLIG, V., STOESS, J., AND GOTZ, S. Un-
modified Device Driver Reuse and Improved System Dependabil-
ity via Virtual Machines. In Proceedings of the 2004 Symposium
USENIX Association
[37]
[38]
[39]
[40]
[41]
[42]
[43]
[44]
[45]
[46]
[47]
[48]
[49]
[50]
[51]
[52]
USENIX Association
on Operating Systems Design and Implementation (OSDI) (De-
cember 2004).
MOSHCHUK, A., BRAGIN, T., GRIBBLE, S. D., AND LEVY,
H. M. A crawler-based study of spyware on the web. In Pro-
ceedings of the 2006 Network and Distributed System Security
Symposium (NDSS) (February 2006).
MOSHCHUK, A., AND WANG, H. J. Resource Management for
Web Applications in ServiceOS. Tech. rep., Microsoft Research,
May 2010.
OKHRAVI, H., AND NICOL, D. M. Trustgraph: Trusted graphics
subsystem for high assurance systems. In ACSAC ’09: Proceed-
ings of the 2009 Annual Computer Security Applications Con-
ference (Washington, DC, USA, 2009), IEEE Computer Society,
pp. 254-265.
PALM INC. webOS, 2010. http://opensource.palm.
com.
PRrovos, N., MAVROMMATIS, P., RAJAB, M. A., AND MON-
ROSE, F. All your iFRAMEs point to us. In Proceedings of the
17th Usenix Security Symposium (July 2008), pp. 1-15.
PRrovos, N., MCNAMEE, D., MAVROMMATIS, P., WANG, K.,
AND MODADUGU, N. The ghost in the browser: Analysis of
Web-based malware. In Proceedings of the 2007 Workshop on
Hot Topics in Understanding Botnets (HotBots) (April 2007).
REIS, C., AND GRIBBLE, S. D. Isolating web programs in mod-
ern browser architectures. In Proceedings of the 2009 EuroSys
conference (2009).
REIS, C., GRIBBLE, S. D., AND LEVy, H. M. Architec-
tural principles for safe web programs. In Proceedings of the
Sixth Workshop on Hot Topics in Networks (HotNets) (November
2007).
SHAPIRO, J. S., SMITH, J. M., AND FARBER, D. J. EROS:
a fast capability system. In SOSP ’99: Proceedings of the sev-
enteenth ACM symposium on Operating systems principles (New
York, NY, USA, 1999), ACM, pp. 170-185.
SHAPIRO, J. S., VANDERBURGH, J., NORTHUP, E., AND CHIZ-
MADIA, D. Design of the EROS trusted window system. In Pro-
ceedings of the 13th conference on USENIX Security Symposium
(Berkeley, CA, USA, 2004), USENIX Association, pp. 12-12.
SINGH, K., MOSHCHUK, A., WANG, H. J., AND LEE, W. On
the incoherencies in web browser access control policies. In Pro-
ceedings of the IEEE Symposium on Security and Privacy (May
2010).
SWIFT, M. M., ANNAMALAI, M., BERSHAD, B. N., AND
LEvy, H. M. Recovering Device Drivers. In Proceedings of
the 2004 Symposium on Operating Systems Design and Imple-
mentation (OSDI) (December 2004).
SWIFT, M. M., BERSHAD, B. N., AND LEvy, H. M. Improv-
ing the reliability of commodity operating systems. In SOSP ’03:
Proceedings of the nineteenth ACM symposium on Operating sys-
tems principles (New York, NY, USA, 2003), ACM, pp. 207-222.
SYMANTEC INC. Symantec global Internet security threat report:
Trends for 2008, April 2009. http://www. symantec.com/
business/theme. jsp?themeid=threatreport.
TAN, L., ZHANG, X., MA, X., XIONG, W., AND ZHOU, Y. Au-
toISES: Automatically inferring security specifications and de-
tecting violations. In Proceedings of the 17th USENIX Security
Symposium (USENIX Security ’08) (July-August 2008).
WANG, H. J., GRIER, C., MOSHCHUK, A., KING, S. T.,
CHOUDHURY, P., AND VENTER, H. The multi-principal OS
construction of the Gazelle web browser. In Proceedings of the
2009 USENIX Security Symposium (August 2009).
[53]
[54]
[55]
[56]
[57]
[58]
WANG, Y.-M., BECK, D., JIANG, X., ROUSSEV, R., VER-
BOWSKI, C., CHEN, S., AND KING, S. Automated Web Pa-
trol with Strider HoneyMonkeys: Finding Web sites that exploit
browser vulnerabilities. In Proceedings of the 2006 Network and
Distributed System Security Symposium (NDSS) (February 2006).
WHEELER, D. SLOCcount, 2009. http: //www.dwheeler.
com/sloccount/.
WILLIAMS, D., REYNOLDS, P., WALSH, K., SIRER, E. G.,
AND SCHNEIDER, F. B. Device driver safety through a reference
validation mechanism. In OSDI 08: Proceedings of the 8th sym-
posium on operating systems design and implementation (2008).
WOODWARD, J. P. Security requirementes for systems high and
compartemented mode workstations. Tech. rep., MITRE Corp.,
1987. MTR 9992.
ZELDOVICH, N., BOYD-WICKIZER, S., KOHLER, E., AND
MAZIERES, D. Making information flow explicit in HiStar.
In OSDI ’06: Proceedings of the 7th symposium on Operating
systems design and implementation (Berkeley, CA, USA, 2006),
USENIX Association, pp. 263-278.
ZHOU, F., CONDIT, J., ANDERSON, Z., BAGRAK, I., EN-
NALS, R., HARREN, M., NECULA, G., AND BREWER, E.
Safedrive: safe and recoverable extensions using language-based
techniques. In OSDI ’06: Proceedings of the 7th symposium
on Operating systems design and implementation (Berkeley, CA,
USA, 2006), USENIX Association, pp. 45-60.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) 31
USENIX Association
FlexSC: Flexible System Call Scheduling with Exception-Less System Calls
Livio Soares
University of Toronto
Abstract
For the past 30+ years, system calls have been the de facto
interface used by applications to request services from the
operating system kernel. System calls have almost uni-
versally been implemented as a synchronous mechanism,
where a special processor instruction is used to yield user-
space execution to the kernel. In the first part of this
paper, we evaluate the performance impact of traditional
synchronous system calls on system intensive workloads.
We show that synchronous system calls negatively affect
performance in a significant way, primarily because of
pipeline flushing and pollution of key processor structures
(e.g., TLB, data and instruction caches, etc.).
We propose a new mechanism for applications to
request services from the operating system kernel:
exception-less system calls. They improve processor effi-
ciency by enabling flexibility in the scheduling of operat-
ing system work, which in turn can lead to significantly in-
creased temporal and spacial locality of execution in both
user and kernel space, thus reducing pollution effects on
processor structures. Exception-less system calls are par-
ticularly effective on multicore processors. They primar-
ily target highly threaded server applications, such as Web
servers and database servers.
We present FlexSC, an implementation of exception-
less system calls in the Linux kernel, and an accompany-
ing user-mode thread package (FlexSC-Threads), binary
compatible with POSIX threads, that translates legacy
synchronous system calls into exception-less ones trans-
parently to applications. We show how FlexSC improves
performance of Apache by up to 116%, MySQL by up to
40%, and BIND by up to 105% while requiring no modi-
fications to the applications.
1 Introduction
System calls are the de facto interface to the operating sys-
tem kernel. They are used to request services offered by,
and implemented in the operating system kernel. While
Michael Stumm
University of Toronto
Syscall impact on user-mode IPC
=
ons =
oer Wn
Lost performance (cycles)
User-mode IPC
(higher is faster)
o
q
2
win
Syscall exception
0.
0 2000 4000 6000 8000 10000 12000 14000 16000
Time (in cycles)
Figure 1: User-mode instructions per cycles (IPC) of Xalan
(from SPEC CPU 2006) in response to a system call exception
event, as measured on an Intel Core 17 processor.
different operating systems offer a variety of different ser-
vices, the basic underlying system call mechanism has
been common on all commercial multiprocessed operat-
ing systems for decades. System call invocation typically
involves writing arguments to appropriate registers and
then issuing a special machine instruction that raises a
synchronous exception, immediately yielding user-mode
execution to a kernel-mode exception handler. Two im-
portant properties of the traditional system call design are
that: (1) a processor exception is used to communicate
with the kernel, and (2) a synchronous execution model is
enforced, as the application expects the completion of the
system call before resuming user-mode execution. Both of
these effects result in performance inefficiencies on mod-
ern processors.
The increasing number of available transistors on a chip
(Moore’s Law) has, over the years, led to increasingly
sophisticated processor structures, such as superscalar
and out-of-order execution units, multi-level caches, and
branch predictors. These processor structures have, in
turn, led to a large increase in the performance poten-
tial of software, but at the same time there is a widening
gap between the performance of efficient software and the
performance of inefficient software, primarily due to the
increasing disparity of accessing different processor re-
sources (e.g., registers vs. caches vs. memory). Server
and system-intensive workloads, which are of particular
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) 33
34
interest in our work, are known to perform well below the
potential processor throughput [11, 12, 19]. Most studies
attribute this inefficiency to the lack of locality. We claim
that part of this lack of locality, and resulting performance
degradation, stems from the current synchronous system
call interface.
Synchronous implementation of system calls negatively
impacts the performance of system intensive workloads,
both in terms of the direct costs of mode switching and,
more interestingly, in terms of the indirect pollution of
important processor structures which affects both user-
mode and kernel-mode performance. A motivating ex-
ample that quantifies the impact of system call pollution
on application performance can be seen in Figure |. It
depicts the user-mode instructions per cycles (kernel cy-
cles and instructions are ignored) of one of the SPEC CPU
2006 benchmarks (Xalan) immediately before and after a
pwrite system call. There is a significant drop in in-
structions per cycle (IPC) due to the system call, and it
takes up to 14,000 cycles of execution before the IPC of
this application returns to its previous level. As we will
show, this performance degradation is mainly due to inter-
ference caused by the kernel on key processor structures.
To improve locality in the execution of system intensive
workloads, we propose a new operating system mecha-
nism: the exception-less system call. An exception-less
system call is a mechanism for requesting kernel services
that does not require the use of synchronous processor ex-
ceptions. In our implementation, system calls are issued
by writing kernel requests to a reserved syscall page, us-
ing normal memory store operations. The actual execu-
tion of system calls is performed asynchronously by spe-
cial in-kernel syscall threads, which post the results of
system calls to the syscall page after their completion.
Decoupling the system call execution from its invoca-
tion creates the possibility for flexible system call schedul-
ing, offering optimizations along two dimensions. The
first optimization allows for the deferred batch execution
of system calls resulting in increased temporal locality of
execution. The second provides the ability to execute sys-
tem calls on a separate core, in parallel to executing user-
mode threads, resulting in spatial, per core locality. In
both cases, system call threads become a simple, but pow-
erful abstraction.
One interesting feature of the proposed decoupled sys-
tem call model is the possibility of dynamic core special-
ization in multicore systems. Cores can become temporar-
ily specialized for either user-mode or kernel-mode execu-
tion, depending on the current system load. We describe
how the operating system kernel can dynamically adapt
core specialization to the demands of the workload.
One important challenge of our proposed system is how
to best use the exception-less system call interface. One
option is to rewrite applications to directly interface with
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
the exception-less system call mechanism. We believe the
lessons learned by the systems community with event-
driven servers indicate that directly using exception-less
system calls would be a daunting software engineer-
ing task. For this reason, we propose a new M-on-N
threading package (JV user-mode threads executing on NV
kernel-visible threads, with 1/ >> N). The main purpose
of this threading package is to harvest independent sys-
tem calls by switching threads, in user-mode, whenever a
thread invokes a system call.
This research makes the following contributions:
e We quantify, at fine granularity, the impact of syn-
chronous mode switches and system call execution on
the micro-architectural processor structures, as well as
on the overall performance of user-mode execution.
e We propose a new operating system mechanism, the
exception-less system call, and describe an implemen-
tation, FlexSC!, in the Linux kernel.
e We present a M-on-N threading system, compati-
ble with PThreads, that transparently uses the new
exception-less system call facility.
e We show how exception-less system calls coupled with
our M-on-N threading system improves performance
of important system-intensive highly threaded work-
loads: Apache by up to 116%, MySQL by to 40%, and
BIND by up to 105%.
2 The (Real) Costs of System Calls
In this section, we analyze the performance costs associ-
ated with a traditional, synchronous system call. We ana-
lyze these costs in terms of mode switch time, the system
call footprint, and the effect on user-mode and kernel-
mode IPC. We used the Linux operating system kernel
and an Intel Nehalem (Core i7) processor, along with its
performance counters to obtain our measurements. How-
ever, we believe the lessons learned are applicable to most
modern high-performance processors” and other operat-
ing system kernels.
2.1 Mode Switch Cost
Traditionally, the performance cost attributed to system
calls is the mode switch time. The mode switch time con-
sists of the time necessary to execute the appropriate sys-
tem call instruction in user-mode, resuming execution in
an elevated protection domain (kernel-mode), and the re-
turn of control back to user-mode. Modern processors im-
plement the mode switch as a processor exception: flush-
ing the user-mode pipeline, saving a few registers onto the
Pronounced as “flex” (/’fleks/).
2Experiments performed on an older PowerPC 970 processor yielded
similar insights than the ones presented here.
USENIX Association
Syscall Instructions | Cycles | IPC | i-cache | d-cache L2 L3 | d-TLB
stat 4972 | 13585 | 0.37 32 186 660 | 2559 21
pread 3739 12300 | 0.30 32. 294 679 | 2160 20
pwrite 5689 | 31285 | 0.18 50 373 985 | 3160 44
open+close 6631 19162 | 0.34 47 240 900 | 3534 28
mmap-+munmap 8977 | 19079 | 0.47 41 233 869 | 3913 7
open+write+close 9921 32815 | 0.30 78 481 1462 | 5105 49
Table 1: System call footprint of different processor structures. For the processors structures (caches and TLB), the numbers represent
number of entries evicted; the cache line for the processor is of 64-bytes. i-cache and d-cache refer to the instruction and data sections
USENIX Association
of the L1 cache, respectively. The d-TLB represents the data portion of the TLB.
kernel stack, changing the protection domain, and redi-
recting execution to the registered exception handler. Sub-
sequently, return from exception is necessary to resume
execution in user-mode.
We measured the mode switch time by implement-
ing a new system call, gettsc that obtains the time
stamp counter of the processor and immediately returns
to user-mode. We created a simple benchmark that in-
voked gettsc | billion times, recording the time-stamp
before and after each call. The difference between each
of the three time-stamps identifies the number of cycles
necessary to enter and leave the operating system kernel,
namely 79 cycles and 71 cycles, respectively. The total
round-trip time for the gett sc system call is modest at
150 cycles, being less than the latency of a memory ac-
cess that misses the processor caches (250 cycles on our
machine).?
2.2 System Call Footprint
The mode switch time, however, is only part of the cost of
a system call. During kernel-mode execution, processor
structures including the L1 data and instruction caches,
translation look-aside buffers (TLB), branch prediction ta-
bles, prefetch buffers, as well as larger unified caches (L2
and L3), are populated with kernel specific state. The re-
placement of user-mode processor state by kernel-mode
processor state is referred to as the processor state pollu-
tion caused by a system call.
To quantify the pollution caused by system calls, we
used the Core i7 hardware performance counters (HPC).
We ran a high instruction per cycle (IPC) workload,
Xalan, from the SPEC CPU 2006 benchmark suite that
is known to invoke few system calls. We configured an
HPC to trigger infrequently (once every 10 million user-
mode instructions) so that the processor structures would
be dominated with application state. We then set up the
HPC exception handler to execute specific system calls,
while measuring the replacement of application state in
the processor structures caused by kernel execution (but
not by the performance counter exception handler itself).
3For all experiments presented in this paper, user-mode applications
execute in 64-bit mode and when using synchronous system calls, use
the “syscall” x86_64 instruction, which is currently the default in Linux.
Table | shows the footprint on several processor struc-
tures for three different system calls and three system call
combinations. The data shows that, even though the num-
ber of i-cache lines replaced is modest (between 2 and
5 KB), the number of d-cache lines replaced is signifi-
cant. Given that the size of the d-cache on this processor
is 32 KB, we see that the system calls listed pollute at
least half of the d-cache, and almost all of the d-cache in
the “open-+write+close” case. The 64 entry first level d-
TLB is also significantly polluted by most system calls.
Finally, it is interesting to note that the system call impact
on the L2 and L3 caches is larger than on the L1 caches,
primarily because the L2 and L3 caches use more aggres-
sive prefetching.
2.3 System Call Impact on User IPC
Ultimately, the most important measure of the real cost
of system calls is the performance impact on the applica-
tion. To quantify this, we executed an experiment similar
to the one described in the previous subsection. However,
instead of measuring kernel-mode events, we only mea-
sured user-mode instructions per cycle (IPC), ignoring all
kernel execution. Ideally, user-mode IPC should not de-
crease as a result of invoking system calls, since the cy-
cles and instructions executed as part of the system call
are ignored in our measurements. In practice, however,
user-mode IPC is affected by two sources of overhead:
Direct: The processor exception associated with the sys-
tem call instruction that flushes the processor pipeline.
Indirect: System call pollution on the processor struc-
tures, as quantified in Table 1.
Figures 2 and 3 show the degradation in user-mode IPC
when running Xalan (from SPEC CPU 2006) and SPEC-
JBB, respectively, given different frequencies of pwrite
calls. These benchmarks were chosen since they have
been created to avoid significant use of system services,
and should spend only 1-2% of time executing in kernel-
mode. The graphs show that different workloads can have
different sensitivities to system call pollution. Xalan has
a baseline user-mode IPC of 1.46, but the IPC degrades
by up to 65% when executing a pwrite every 1,000-
2,000 instructions, yielding an IPC between 0.58 and 0.50.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) 35
36
Bi Indirect
O Direct
Degradation
(lower is faster)
1K 2K 5K 10K 20K 50K 100K 500K
instructions between interrupts (log scale)
Figure 2: System call (pwrite) impact on user-mode IPC as a
function of system call frequency for Xalan.
, Of Indirect
40% O Direct
Degradation
(lower is faster)
1K 2K 5K 10K 20K 50K 100K 500K
instructions between interrupts (log scale)
Figure 3: System call (pwrite) impact on user-mode IPC as a
function of system call frequency for SPEC JBB.
SPEC-JBB has a slightly lower baseline of 0.97, but still
observes a 45% degradation of user-mode IPC.
The figures also depict the breakdown of user-mode
IPC degradation due to direct and indirect costs. The
degradation due to the direct cost was measured by issu-
ing a null system call, while the indirect portion is cal-
culated subtracting the direct cost from the degradation
measured when issuing a pwrite system call. For high
frequency system call invocation (once every 2,000 in-
structions, or less), the direct cost of raising an exception
and subsequent flushing of the processor pipeline is the
largest source of user-mode IPC degradation. However,
for medium frequencies of system call invocation (once
per 2,000 to 100,000 instructions), the indirect cost of sys-
tem calls is the dominant source of user-mode IPC degra-
dation.
To understand the implication of these results on typi-
cal server workloads, it is necessary to quantify the sys-
tem call frequency of these workloads. The average user-
mode instruction count between consecutive system calls
for three popular server workloads are shown in Table 2.
For this frequency range in Figures 2 and 3 we observe
user-mode IPC performance degradation between 20%
and 60%. While the excecution of the server workloads
listed in Table 2 is not identical to that of Xalan or SPEC-
Workload (server) Instructions per Syscall
DNSbench (BIND) 2445
ApacheBench (Apache) 3368
Sysbench (MySQL) 12435
Table 2: The average number of instructions executed on differ-
ent workloads before issuing a syscall.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
80%
70%
60%
a
S
SS
0%
0%
Degradation
(lower is faster
oh
nN
S
xs
100 500 1K 2K 5K 10K 20K 50K 100K 500K
instructions between interrupts (log scale)
Figure 4: System call (pwrite), impact on kernel-mode IPCs
for x as a function of system call frequency.
JBB, the data presented here indicates that server work-
loads suffer from significant performance degradation due
to processor pollution of system calls.
2.4 Mode Switching Cost on Kernel IPC
The lack of locality due to frequent mode switches also
negatively affects kernel-mode IPC. Figure 4 shows the
impact of different system call frequencies on the kernel-
mode IPC. As expected, the performance trend is opposite
to that of user-mode execution. The more frequent the
system calls, the more kernel state is maintained in the
processor.
Note that the kernel-mode IPC listed in Table | for dif-
ferent system calls ranges from 0.18 to 0.47, with an av-
erage of 0.32. This is significantly lower than the 1.47
and 0.97 user-mode IPC for Xalan and SPEC-JBB, re-
spectively; up to 8x slower.
3 Exception-Less System Calls
To address (and partially eliminate) the performance im-
pact of traditional, synchronous system calls on system
intensive workloads, we propose a new operating system
mechanism called exception-less system call. Exception-
less system call is a mechanism for requesting kernel ser-
vices that does not require the use of synchronous pro-
cessor exceptions. The key benefit of exception-less sys-
tem calls is the flexibility in scheduling system call execu-
tion, ultimately providing improved locality of execution
of both user and kernel code. We explore two use cases:
System call batching: Delaying the execution of a series
of system calls and executing them in batches minimizes
the frequency of switching between user and kernel execu-
tion, eliminating some of the mode switch overhead and
allowing for improved temporal locality. This improves
both the direct and indirect costs of system calls.
Core specialization: In multicore systems, exception-
less system calls allow a system call to be scheduled on
a core different than the one on which the system call was
invoked. Scheduling system calls on a separate processor
core allows for improved spatial locality and with it lower
USENIX Association
USENIX Association
ak ae OS
User User a
-<--<=5 Sao ofos==. Ssees<=====S-—] ca
Kernel 27h Js Kernel ae
Exception! Exception! pag
KZ
(a) Traditional, sync. system call (b) Exception-less system call
Figure 5: Illustration of synchronous and exception-less system
call invocation. The left diagram shows the sequential nature
of exception-based system calls, while the right diagram depicts
exception-less user and kernel communication through shared
memory.
syscall | number of return
status | argO | e e e | arg6
number | arguments value
Figure 6: 64-byte syscall entry from the syscall page.
indirect costs. In an ideal scenario, no mode switches are
necessary, eliminating the direct cost of system calls.
The design of exception-less system calls consists of
two components: (1) an exception-less interface for user-
space threads to register system calls, along with (2) an
in-kernel threading system that allows the delayed (asyn-
chronous) execution of system calls, without interrupting
or blocking the thread in user-space.
3.1 Exception-Less Syscall Interface
The interface for exception-less system calls is simply a
set of memory pages that is shared amongst user and ker-
nel space. The shared memory page, henceforth referred
to as syscall page, is organized to contain exception-less
system call entries. Each entry contains space for the re-
quest status, system call number, arguments, and return
value.
With traditional synchronous system calls, invocation
occurs by populating predefined registers with system call
information and issuing a specific machine instruction that
immediately raises an exception. In contrast, to issue an
exception-less system call, the user-space threads must
find a free entry in the syscall page and populate the en-
try with the appropriate values using regular store instruc-
tions. The user-space thread can then continue executing
without interruption. It is the responsibility of the user-
space thread to later verify the completion of the system
call by reading the status information in the entry. None
of these operations, issuing a system call or verifying its
completion, causes exceptions to be raised.
3.2 Syscall Pages
Syscall pages can be viewed as a table of syscall en-
tries, each containing information specific to a single sys-
tem call request, including the system call number, ar-
guments, status (free/submitted/busy/done), and the result
(Figure 6). In our 64-bit implementation, we have orga-
nized each entry to occupy 64 bytes. This size comes from
the Linux ABI which allows any system call to have up to
6 arguments, and a return value, totalling 56 bytes. Al-
though the remaining 3 fields (syscall number, status and
number of arguments) could be packed in less than the
remaining 8 bytes, we selected 64 bytes because 64 is a
divisor of popular cache line sizes of today’s processor.
To issue an exception-less system call, the user-space
thread must find an entry in one of its syscall pages that
contain a free status field. It then writes the syscall num-
ber and arguments to the entry. Lastly, the status field is
changed to submitted’, indicating to the kernel that the re-
quest is ready for execution. The thread must then check
the status of the entry until it becomes done, consume the
return value, and finally set the status of the entry to free.
3.3. Decoupling Execution from Invocation
Along with the exception-less interface, the operating sys-
tem kernel must support delayed execution of system
calls. Unlike exception-based system calls, the exception-
less system call interface does not result in an explicit ker-
nel notification, nor does it provide an execution stack. To
support decoupled system call execution, we use a spe-
cial type of kernel thread, which we call syscall thread.
Syscall threads always execute in kernel mode, and their
sole purpose is to pull requests from syscall pages and ex-
ecute them on behalf of the user-space thread. Figure 5
illustrates the difference between traditional synchronous
system calls, and our proposed split system call model.
The combination of the exception-less system call in-
terface and independent syscall threads allows for great
flexibility in the scheduling the execution of system calls.
Syscall threads may wake up only after user-space is un-
able to make further progress, in order to achieve tempo-
ral locality of execution on the processor. Orthogonally,
syscall threads can be scheduled on a different processor
core than that of the user-space thread, allowing for spa-
tial locality of execution. On modern multicore proces-
sors, cache to cache communication is relatively fast (in
the order of 10s of cycles), so communicating the entries
of syscall pages from a user-space core to a kernel core, or
vice-versa, should only cause a small number of processor
stalls.
3.4 Implementation — FlexSC
Our implementation of the exception-less system call
mechanism is called FlexSC (Flexible System Call) and
was prototyped as an extension to the Linux kernel. Al-
though our implementation was influenced by a mono-
4User-space must update the status field last, with an appropriate
memory barrier, to prevent the kernel from selecting incomplete syscall
entries to execute.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) 37
38
lithic kernel architecture, we believe that most of our de-
sign could be effective with other kernel architectures,
e.g., exception-less micro-kernel IPCs, and hypercalls in
a paravirtualized environment.
We have implemented FlexSC for the x86_64 and
PowerPC64 processor architectures. Porting FlexSC to
other architectures is trivial; a single function is needed,
which moves arguments from the syscall page to appropri-
ate registers, according to the ABI of the processor archi-
tecture. Two new system calls were added to Linux as part
of FlexSC, flexsc_register and flexsc_wait.
flexsc_register() This system call is used by pro-
cesses that wish to use the FlexSC facility. Making this
registration procedure explicit is not strictly necessary, as
processes can be registered with FlexSC upon creation.
We chose to make it explicit mainly for convenience of
prototyping, giving us more control and flexibility in user-
space. One legitimate reason for making registration ex-
plicit is to avoid the extra initialization overheads incurred
for processes that do not use exception-less system calls.
Invocation of the flexsc_register system call must
use the traditional, exception-based system call interface
to avoid complex bootstrapping; however, since this sys-
tem call needs to execute only once, it does not impact
application performance. Registration involves two steps:
mapping one or more syscall pages into user-space virtual
memory space, and spawning one syscall thread per entry
in the syscall pages.
flexsc_wait() The decoupled execution model of
exception-less system calls creates a challenge in user-
space execution, namely what to do when the user-space
thread has nothing more to execute and is waiting on
pending system calls. With the proposed execution model,
the OS kernel loses the ability to determine when a user-
space thread should be put to sleep. With synchronous
system calls, this is simply achieved by putting the thread
to sleep while it is executing a system call if the call blocks
waiting for a resource.
The solution we adopted is to require that the user ex-
plicitly communicate to the kernel that it cannot progress
until one of the issued system calls completes by invok-
ing the flexsc_wait system call. We implemented
flexsc_wait as an exception-based system call, since
execution should be synchronously directed to the kernel.
FlexSC will later wake up the user-space thread when at
least one of posted system calls are complete.
3.5 Syscall Threads
Syscall threads is the mechanism used by FlexSC to allow
for exception-less execution of system calls. The Linux
system call execution model has influenced some imple-
mentation aspects of syscall threads in FlexSC: (1) the vir-
tual address space in which system call execution occurs
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
is the address space of the corresponding process, and (2)
the current thread context can be used to block execution
should a necessary resource not be available (for example,
waiting for I/O).
To resolve the virtual address space requirement,
syscall threads are created during flexsc_register.
Syscall threads are thus “cloned” from the registering pro-
cess, resulting in threads that share the original virtual ad-
dress space. This allows the transfer of data from/to user-
space with no modification to Linux’s code.
FlexSC would ideally never allow a syscall thread to
sleep. If a resource is not currently available, notification
of the resource becoming available should be arranged,
and execution of the next pending system call should be-
gin. However, implementing this behavior in Linux would
require significant changes and a departure from the basic
Linux architecture. Instead, we adopted a strategy that al-
lows FlexSC to maintain the Linux thread blocking archi-
tecture, as well as requiring only minor modifications (3
lines of code) to Linux context switching code, by creat-
ing multiple syscall threads for each process that registers
with FlexSC.
In fact, FlexSC spawns as many syscall threads as there
are entries available in the syscall pages mapped in the
process. This provisions for the worst case where ev-
ery pending system call blocks during execution. Spawn-
ing hundreds of syscall threads may seem expensive, but
Linux in-kernel threads are typically much lighter weight
than user threads: all that is needed is a task_struct
and a small, 2-page, stack for execution. All the other
structures (page table, file table, etc.) are shared with the
user process. In total, only 1OKB of memory is needed
per syscall thread.
Despite spawning multiple threads, only one syscall
thread is active per application and core at any given point
in time. If system calls do not block all the work is exe-
cuted by a single syscall thread, while the remaining ones
sleep on a work-queue. When a syscall thread needs to
block, for whatever reason, immediately before it is put
to sleep, FlexSC notifies the work-queue. Another thread
wakes-up and immediately starts executing the next sys-
tem call. Later, when resources become free, current
Linux code wakes up the waiting thread (in our case, a
syscall thread), and resumes its execution, so it can post its
result to the syscall page and return to wait in the FlexSC
work-queue.
3.6 FlexSC Syscall Thread Scheduler
FlexSC implements a syscall thread scheduler that is re-
sponsible for determining when and on which core sys-
tem calls will execute. This scheduler is critical to per-
formance, as it influences the locality of user and kernel
execution.
USENIX Association
USENIX Association
On a single-core environment, the FlexSC scheduler
assumes the user-space will attempt to post as many
exception-less system calls as possible, and subsequently
call flexsc_wait(). The FlexSC scheduler then
wakes up an available syscall thread that starts executing
the first system call. If the system call does not block,
the same syscall thread continues to execute the next sub-
mitted syscall entry. If the execution of a syscall thread
blocks, the currently scheduled syscall thread notifies the
scheduler to wake another thread to continue to execute
more system calls. The scheduler does not wake up the
user-space thread until all available system calls have been
issued, and have either finished or are currently blocked
with at least one system call having been completed. This
is done to minimize the number of mode switches to user-
space.
For multicore execution, the scheduler biases execution
of syscall threads on a subset of available cores, dynam-
ically specializing cores according to the workload re-
quirements. In our current implementation, this is done
by attempting to schedule syscall threads using a prede-
termined, static list of cores. Upon a scheduling decision,
the first core on the list is selected. If a syscall thread of
a process is currently running on that core, the next core
on the list is selected as the target. If the selected core is
not currently executing a syscall thread, an inter-processor
interrupt is sent to the remote core, signalling that it must
wake a syscall thread.
As previously described, there is never more than one
syscall thread concurrently executing per core, for a given
process. However in the multicore case, for the same pro-
cess, there can be as many syscall threads as cores con-
currently executing on the entire system. To avoid cache-
line contention of syscall pages amongst cores, before a
syscall thread begins executing calls from a syscall page,
it locks the page until all its submitted calls have been
issued. Since FlexSC processes typically map multiple
syscall pages, each core on the system can schedule a
syscall thread to work independently, executing calls from
different syscall pages.
4 System Calls Galore — FlexSC-Threads
Exception-less system calls present a significant change to
the semantics of the system call interface with potentially
drastic implications for application code and program-
mers. Programming using exception-less system calls di-
rectly is more complex than using synchronous system
calls, as they do not provide the same, easy-to-reason-
about sequentiality. In fact, our experience is that pro-
gramming using exception-less system calls is akin to
event-driven programming, which has itself been criti-
cized for being a complex programming model [21]. The
main difference is that with exception-less system calls,
not only are I/O related calls scheduled for future comple-
tion, any system calls can be requested, verified for com-
pletion, and handled, as if it were an asynchronous event.
To address the programming complexities, we propose
the use of exception-less system calls in two different
modes that might be used depending on the concurrency
model adopted by the programmer. We argue that if used
according to our recommendations, exception-less sys-
tem calls should pose no more complexity than their syn-
chronous counter-parts.
4.1 Event-driven Servers, a Case for Hybrid
Execution
For event-driven systems, we advocate a hybrid approach
where both synchronous and exception-less system calls
coexist. System calls that are executed in performance
critical paths of applications should use exception-less
calls while all other calls should be synchronous. After
all, there is no good justification to make a simple getpid()
complex to program.
Event-driven servers already have their code structured
so that performance critical paths of execution are split
into three parts: request event, wait for completion and
handle event. Adapting an event-driven server to use
exception-less system calls, for the already considered
events, should be straightforward. However, we have not
yet attempted to evaluate the use of exception-less system
calls in an event-driven program, and leave this as future
work.
4.2 FlexSC-Threads
Multiprocessing has become the default for computation
on servers. With the emergence and ubiquity of multi-
core processors, along with projection of future chip man-
ufacturing technologies, it is unlikely that this trend will
reverse in the medium future. For this reason, and be-
cause of its relative simplicity vis-a-vis event-based pro-
gramming, we believe that the multithreading concur-
rency model will continue to be the norm.
In this section, we describe the design and implementa-
tion of FlexSC-Threads, a threading package that trans-
forms legacy synchronous system calls into exception-
less ones transparently to applications. It is intended
for server-type applications with many user-mode threads,
such as Apache or MySQL. FlexSC-Threads is compli-
ant with POSIX Threads, and binary compatible with
NPTL [8], the default Linux thread library. As a re-
sult, Linux multi-threaded programs work with FlexSC-
Threads “out of the box” without modification or recom-
pilation.
FlexSC-Threads uses a simple M-on-N threading
model (M user-mode threads executing on N kernel-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) 39
40
one kernel-visible thread per ae
multiple syscall
threads per core
multiple user-mode
threads ~~
User z
cal
user-mode switch
All
Th =
—J
Figure 7: The left-most diagram depicts the components of FlexSC-Threads pertaining to a single core. Each core executes a pinned
kernel-visible thread, which in turn can multiplex multiple user-mode threads.
Multiple syscall pages, and consequently syscall
threads, are also allocated (and pinned) per core. The middle diagram depicts a user-mode thread being preempted as a result of
issuing a system call. The right-most diagram depicts the scenario where all user-mode threads are waiting for system call requests;
in this case FlexSC-Threads library synchronously invokes £lexsc_wait () to the kernel.
= ™ ? | 7
Core 0
a
pages
Figure 8: Multicore example. Opaque threads are active, while
grayed-out threads are inactive. Syscall pages are accessible to
both cores, as we run using shared-memory, leveraging the fast
on-chip communication of multicores.
visible threads). We rely on the ability to perform user-
mode thread switching solely in user-space to transpar-
ently transform legacy synchronous calls into exception-
less ones. This is done as follows:
1. We redirect to our library each libc call that issues a
legacy system call. Typically, applications do not di-
rectly embed code to issue system calls, but instead
call wrappers in the dynamically loaded libc. We use
the dynamic loading capabilities of Linux to redirect
execution of such calls to our library.
2. FlexSC-Threads then post the corresponding
exception-less system call to a syscall page and
switch to another user-mode thread that is ready.
3. If we run out of ready user-mode threads, FlexSC
checks the syscall page for any syscall entries that
have been completed, waking up the appropriate
user-mode thread so it can obtain the result of the
completed system call.
4. As a last resort, flexsc_wait () is called, putting
the kernel visible thread to sleep until one of the
pending system calls has completed.
FlexSC-Threads implements multicore support by cre-
ating a single kernel visible thread per core available to
the process, and pinning each kernel visible thread to a
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
specific core. Multiple user-mode threads multiplex exe-
cution on the kernel visible thread. Since kernel-visitble
threads only block when there is no more available work,
there is no need to create more than one kernel visi-
ble thread per core. Figure 7 depicts the components of
FlexSC-Threads and how they interact during execution.
AS an optimization, we have designed FlexSC-Threads
to register a private set of syscall pages per kernel vis-
ible thread (i.e., per core). Since syscall pages are pri-
vate to each core, there is no need to synchronize their
access with costly atomic instructions. The FlexSC-
Threads user-mode scheduler implements a simple form
of cooperative scheduling, with system calls acting as
yield points. Consequently, syscall pages behave as lock-
free single-producer (kernel-visible thread) and single-
consumer (syscall thread) data structures.
From the kernel side, although syscall threads are
pinned to specific cores, they do not only execute system
call requests from syscall pages registered to that core. An
example of this is shown in Figure 8, where user-mode
threads execute on core 0, while syscall threads running
on core | are satisfying system call requests.
It is important to note that FlexSC-Threads relies on a
large number of independent user-mode threads to post
concurrent exception-less system calls. Since threads are
executing independently, there is no constraint on order-
ing or serialization of system call execution (thread-safety
constraints should be enforced at the application level
and is orthogonal to the system call execution model).
FlexSC-Threads leverages the independent requests to ef-
ficiently schedule operating system work on single or mul-
ticore systems. For this reason, highly threaded work-
loads, such as internet/network servers, are ideal candi-
dates for FlexSC-Threads.
5 Experimental Evaluation
We first present the results of a microbenchmark that
shows the overhead of the basic exception-less system
USENIX Association
USENIX Association
Component Specification
Cores 4+
Cache line 64 B for all caches
Private L1 i-cache 32 KB, 3 cycle latency
Private L1 d-cache 32 KB, 4 cycle latency
Private L2 cache 512 KB, 11 cycle latency
Shared L3 cache 8 MB, 35-40 cycle latency
Memory 250 cycle latency (avg.)
TLB (L1) 64 (data) + 64 (instr.) entries
TLB (L2) 512 entries
Table 3: Characteristics of the 2.3GHz Core i7 processor.
90
80 = flexsc
70 == sync
Time (nanoseconds)
>
o
0 10 20 30 40 50 60 70
Number of batched requests
Figure 9: Exception-less system call cost on a single-core.
call mechanism, and then we show the performance of
two popular server applications, Apache and MySQL,
transparently using exception-less system calls through
FlexSC-Threads. Finally, we analyze the sensitivity of
the performance of FlexSC to the number of system call
pages.
FlexSC was implemented in the Linux kernel, version
2.6.33. The baseline line measurements we present were
collected using unmodified Linux (same version), and the
default native POSIX threading library (NPTL). We iden-
tify the baseline configuration as “sync”, and the system
with exception-less system calls as “flexse”.
The experiments presented in this section were run on
an Intel Nehalem (Core i7) processor with the character-
istics shown in Table 3. The processor has 4 cores, each
with 2 hyper-threads. We disabled the hyper-threads, as
well as the “TurboBoost” feature, for all our experiments
to more easily analyze the measurements obtained.
For the Apache and MySQL experiments, requests
were generated by a remote client connected to our test
machine through a | Gbps network, using a dedicated
router. The client machine contained a dual core Core2
processor, running the same Linux installation as the test
machine, and was not CPU or network constrained in any
of the experiments.
All values reported in our evaluation represent the av-
erage of 5 separate runs.
5.1 Overhead
The overhead of executing an exception-less system call
involves switching to a syscall thread, de-marshalling ar-
guments from the appropriate syscall page entry, switch-
700
600
= flexsc
= sync (same
500 core)
Time (nanoseconds)
0 10 20 30 40 50 60 70
Number of batched requests
Figure 10: Exception-less system call cost, in the worst case, for
remote core execution.
ing back to the user-thread, and retrieving the return value
from the syscall page entry. To measure this overhead,
we created a micro-benchmark that successively invokes a
getppid() system call. Since the user and kernel foot-
prints of this call is small, the time measured corresponds
to the direct cost of issuing system calls.
We varied the number of batched system calls, in the
exception-less case, to verify if the direct costs are amor-
tized when batching an increasing number of calls. The
results obtained executing on a single core are shown in
Figure 9. The baseline time, show as a horizontal line, is
the time to execute an exception-based system call on a
single core. Executing a single exception-less system call
on a single core is 43% slower than a synchronous call.
However, when batching 2 or more calls there is no over-
head, and when batching 32 or more calls, the execution
of each call is up to 130% faster than a synchronous call.
We also measured the time to execute system calls on
a remote core (Figure 10). In addition to the single core
operations, remote core execution entails sending an inter-
processor interrupt (IPI) to wake up the remote syscall
thread. In the remote core case, the time to issue a sin-
gle exception-less system call can be more than 10 times
slower than a synchronous system call on the same core.
This measurement represents a worst case scenario when
there is no currently executing syscall thread. Despite the
high overhead, the overhead on remote core execution is
recouped when batching 32 or more system calls.
5.2 Apache
We used Apache version 2.2.15 to evaluate the perfor-
mance of FlexSC-Threads. Since FlexSC-Threads is bi-
nary compatible with NPTL, we used the same Apache
binary for both FlexSC and Linux/NPTL experiments.
We configured Apache to use a different maximum num-
ber of spawned threads for each case. The performance
of Apache running on NPTL degrades with too many
threads, and we experimentally determined that 200 was
optimal for our workload and hence used that configura-
tion for the NPTL case. For the FlexSC-Threads case, we
raised the maximum number of threads to 1000.
The workload we used was ApacheBench, a HTTP
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) 41
42
45000 45000
45000
$ 40000 wflexsc 840000 se $ 40000
35000 “sync _ 35000 syne 35000
% 30000 8 30000 $ 30000
25000 & 25000 = 25000
= 20000 = 20000 = 20000
= 2 =
3 15000 2 15000 2 15000
5, 10000 & 10000 10000 =a
2 5000 3 5000; 3 5000 exse|
7 0 7 i = 0 sync
a 0 200 400 600 800 1000 E 0 200 400 600 800 1000 FE 0 200 400 600 800 1000
Request Concurrency Request Concurrency Request Concurrency
(a) 1 Core (b) 2 Cores (c) 4 Cores
Figure 11: Comparison of Apache throughput of Linux/NPTL and FlexSC executing on 1, 2 and 4 cores.
100%
90%
80%
70%
60%
50%
40%
30%
20%
10%
0%
100%
90%
80%
70%
60%
50%
40%
30%
20%
10%
0%
sync flexsc syne
(b) MySQL
idle
user
W kernel
Time
a
c
G
g
Time
flexsc
(a) Apache
Figure 12: Breakdown of execution time of Apache and MySQL
workloads on 4 cores.
25
Hi sync
20 Bi flexsc
Latency (ms)
1 core 2 cores 4 cores
Figure 13: Comparison of Apache latency of Linux/NPTL and
FlexSC executing on 1, 2 and 4 cores, with 256 concurrent re-
quests.
workload generator that is distributed with Apache. It
is designed to stress-test the Web server determining the
number of requests per second that can be serviced, with
varying number of concurrent requests.
Figure |1 shows the results of Apache running on 1, 2
and 4 cores. For the single core experiments, FlexSC em-
ploys system call batching, and for the multicore experi-
ments it additionally dynamically redirects system calls to
maximize core locality. The results show that, except for
a very low number of concurrent requests, FlexSC outper-
forms Linux/NPTL by a wide margin. With system call
batching alone (1 core case), we observe a throughput im-
provement of up to 86%. The 2 and 4 core experiments
show that FlexSC achieves up to 116% throughput im-
provement, showing the added benefit of dynamic core
specialization.
Table 4 shows the effects of FlexSC on the microarchi-
tectural state of the processor while running Apache. It
displays various processor metrics, collected using hard-
ware performance counters during execution with 512
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
concurrent requests. The most important metric listed
is the instruction per cycles (IPC) of the user and ker-
nel mode for the different setups, as it summarizes the
efficiency of execution. The other values listed are nor-
malized values using misses per kilo-instructions (MPKI).
MPKI is a widely used normalization method that makes
it easy to compare values obtained from different execu-
tions.
The most efficient execution of the four listed in the
table is FlexSC on | core, yielding an IPC of 0.94 on both
kernel and user execution, which is 95—108% higher than
for NPTL. While the FlexSC execution of Apache on 4
cores is not as efficient as the single core case, with an
average IPC of 0.75, there is still an 71% improvement,
on average, over NPTL.
Most metrics we collected are significantly improved
with FlexSC. Of particular importance are the perfor-
mance critical structures that have a high MPKI value
on NPTL such as d-cache, i-cache, and L2 cache. The
better use of these microarchitectural structures effec-
tively demonstrates the premise of this work, namely that
exception-less system calls can improve processor effi-
ciency. The only structure which observes more misses
on FlexSC is the user-mode TLB. We are currently inves-
tigating the reason for this.
There is an interesting disparity between the through-
put improvement (94%) and the IPC improvement (71%)
in the 4 core case. The difference comes from the added
benefit of localizing kernel execution with core specializa-
tion. Figure 12a shows the time breakdown of Apache ex-
ecuting on 4 cores. FlexSC execution yields significantly
less idle time than the NPTL execution.° The reduced
idle time is a consequence of lowering the contention
on a specific kernel semaphore. Linux protects address
spaces with a per address-space read-write semaphore
(mmap_sem). Profiling shows that every Apache thread
allocates and frees memory for serving requests, and both
of these operations require the semaphore to be held with
write permission. Further, the network code in Linux in-
vokes copy__user (), which transfers data in and out
of the user address-space. This function verifies that the
user-space memory is indeed valid, and to do so acquires
5The execution of Apache on 1 or 2 core did not present idle time.
USENIX Association
USENIX Association
Apache User Kernel |
Setup IPC | L3 | 12 d-cache | i-cache | TLB | Branch | IPC | L3 | L2 | d-cache | i-cache | TLB | Branch
sync (1 core) 0.48 | 3.7 | 68.9 63.8 | 130.8 | 7.7 20.9 | 0.45 | 1.4 | 80.0 78.2 | 159.6) 4.6 15.7 |
flexsc (1 core) | 0.94 | 1.7 | 27.5 35.3 41.3 | 8.8 12.6 | 0.94 | 1.0 | 15.8 31.6 45.2] 3.3 11.2 |
sync (4 cores) | 0.45 | 3.9 | 64.6 67.9 | 127.6} 9.6 20.2 | 0.43 | 4.4 | 49.5 73.8 | 1249) 44 15.2 |
flexsc (4 cores) | 0.74 | 1.0 | 37.5 55.5 49.4 | 19.3 13.0 | 0.76 | 1.5 | 19.1 50.2 63.7 | 4.2 11.6 |
Table 4: Micro-architectural breakdown of Apache execution on uni- and quad-core setups. All values shown, except for IPC, are
normalized using misses per kilo-instruction (MPKI): therefore, lower numbers yield more efficient execution and higher IPC.
the semaphore with read permissions. In the NPTL case,
threads from all 4 cores compete on this semaphore, re-
sulting in 50% idle time. With FlexSC, kernel code is
dynamically scheduled to run predominantly on 2 out of
the 4 cores, halving the contention to this resource, elimi-
nating 38% of the original idle time.
Another important metric for servicing Web requests
besides throughput is latency of individual requests. One
might intuitively expect that latency of requests to be
higher under FlexSC because of batching and asyn-
chronous servicing of system calls, but the opposite is the
case. Figure 13 shows the average latency of requests
when processing 256 concurrent requests (other concur-
rency levels showed similar trends). The results show that
Web requests on FlexSC are serviced within 50-60% of
the time needed on NPTL, on average.
5.3 MySQL
In the previous section, we demonstrated the effectiveness
of FlexSC running on a workload with a significant pro-
portion of kernel time. In this section, we experiment with
OLTP on MySQL, a workload for which the proportion of
kernel execution is smaller (roughly 25%). Our evaluation
used MySQL version 5.5.4 with an InnoDB backend en-
gine, and as in the Apache evaluation, we used the same
binary for running on NPTL and on FlexSC. We also used
the same configuration parameters for both the NPTL and
FlexSC experiments, after tuning them for the best NPTL
performance.
To generate requests to MySQL, we used the sysbench
system benchmark utility. Sysbench was created for
benchmarking MySQL processor performance and con-
tains an OLTP inspired workload generator. The bench-
mark allows executing concurrent requests by spawning
multiple client threads, connecting to the server, and se-
quentially issuing SQL queries. To handle the concurrent
clients, MySQL spawns a user-level thread per connec-
tion. At the end, sysbench reports the number of trans-
actions per second executed by the database, as well as
average latency information. For these experiments, we
used a database with 5M rows, resulting in 1.2 GB of data.
Since we were interested in stressing the CPU component
of MySQL, we disabled synchronous transactions to disk.
Given that the configured database was small enough to
fit in memory, the workload presented no idle time due to
disk I/O.
Figure 14 shows the throughput numbers obtained on
1, 2 and 4 cores when varying the number of concur-
rent client threads issuing requests to the MySQL server.°
For this workload, system batching on one core provides
modest improvements: up to 14% with 256 concurrent re-
quests. On 2 and 4 cores, however, we see that FlexSC
provides a consistent improvement with 16 or more con-
current clients, achieving up to 37%-40% higher through-
put.
Table 5 contains the microarchitectural processor met-
rics collected for the execution of MySQL. Because
MySQL invokes the kernel less frequently than Apache,
kernel execution yields high miss rates, resulting in a low
IPC of 0.33 on NPTL. In the single core case, FlexSC does
not greatly alter the execution of user-space, but increases
kernel IPC by 36%. FlexSC allows the kernel to reuse
state in the processor structures, yielding lower misses
across most metrics. In the case of 4 cores, FlexSC also
improves the performance of user-space IPC by as much
as 30%, compared to NPTL. Despite making less of an
impact in the kernel IPC than in single core execution,
there is still a 25% kernel IPC improvement over NPTL.
Figure 15 shows the average latencies of individual re-
quests for MySQL execution with 256 concurrent clients.
As is the case with Apache, the latency of requests on
FlexSC is improved over execution on NPTL. Requests
on FlexSC are satisfied within 70-88% of the time used
by requests on NPTL.
5.4 Sensitivity Analysis
In all experiments presented so far, FlexSC was config-
ured to have 8 system call pages per core, allowing up to
512 concurrent exception-less system calls per core.
Figure 16 shows the sensitivity of FlexSC to the num-
ber of available syscall entries. It depicts the throughput
of Apache, on | and 4 cores, while servicing 2048 concur-
rent requests per core, so that there would always be more
requests available than syscall entries. Uni-core perfor-
mance approaches its best with 200 to 250 syscall entries
For both NPTL and FlexSC, increasing the load on MySQL yields
peak throughput between 32 and 128 concurrent clients after which
throughput degrades. The main reason for performance degradation is
the costly and coarse synchronization used in MySQL. MySQL and
Linux kernel developers have observed similar performance degradation.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) 43
44
)
)
~ 700
600
500
400
300
200,
Throughput (requests/sec.
1000
900
800
700
600
500
400
300
Throughput (requests/sec.)
100 200
: is cee Fie se
0 50 100 = 150 200280300 0 50 100 = 150-200-250. 300 0 50 100 = 150-200-250 300
Request Concurrency Request Concurrency Request Concurrency
(a) 1 Core (b) 2 Cores (c) 4 Cores
Figure 14: Comparison of MySQL throughput of Linux/NPTL and FlexSC executing on 1, 2 and 4 cores.
MySQL User Kernel
Setup IPC | L3 | L2 | d-cache | i-cache | TLB | Branch | IPC | L3 L2 | d-cache | i-cache | TLB | Branch
sync (1 core) 1.12 | 0.6 | 21.1 34.8 24.2 | 3.8 7.8 | 0.33 | 16.5 | 125.2 209.6 | 184.9) 3.9 17.4
flexsc (1 core) | 1.10 | 0.8 | 19.6 36.3 23.6| 5.4 6.9 | 0.45 | 23.2] 55.1 131.9 86.5 | 3.7 13.6
sync (4 cores) | 0.55 | 3.7 | 15.8 2.2 18.9) 3.1 5.9 | 0.36 | 16.6 | 78.0 147.0} 120.0) 3.6 15.7
flexsc (4 cores) | 0.72 | 2.7 | 16.7 30.6 20.9] 4.1 6.5 | 0.45 | 18.4 | 46.6 104.4 63.5] 2.5 11.5
Table 5: Micro-architectural breakdown of MySQL execution on uni- and quad-core setups. All values shown, except for IPC, are
normalized using misses per kilo-instruction (MPKI): therefore, lower numbers yield more efficient execution and higher IPC.
Bi sync
Bi flexsc
Latency (ms)
1 core 2 cores 4 cores
Figure 15: Comparison of MySQL latency of Linux/NPTL and
FlexSC executing on 1, 2 and 4 cores, with 256 concurrent re-
quests.
50000
45000
40000
35000
30000
25000
20000
15000
10000
5000
a ‘ “i 4 cores
= 4-core
Throughput (requests/sec.)
0 100 200 300 400 500
Number of syscall entries (per core)
600
Figure 16: Execution of Apache on FlexSC-Threads, showing
the performance sensitivity of FlexSC to different number of
syscall pages. Each syscall page contains 64 syscall entries.
(3 to 4 syscall pages), while quad-core execution starts
to plateau with 300 to 400 syscall entries (6 to 7 syscall
pages).
It is particularly interesting to compare Figure 16 with
figures 9 and 10. The direct cost of mode switching, ex-
emplified by the micro-benchmark, has a lesser effect on
performance when compared to the indirect cost of mix-
ing user- and kernel-mode execution.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
6 Related Work
6.1 System Call Batching
The idea of batching calls in order to save crossings
has been extensively explored in the systems community.
Specific to operating systems, multi-calls are used in both
operating systems and paravirtualized hypervisors as a
mechanism to address the high overhead of mode switch-
ing. Cassyopia is a compiler targeted at rewriting pro-
grams to collect many independent system calls, and sub-
mitting them as a single multi-call [18]. An interesting
technique in Cassyopia, which could be eventually ex-
plored in conjunction with FlexSC, is the concept of a
looped multi-call where the result of one system call can
be automatically fed as an argument to another system call
in the same multi-call. In the context of hypervisors, both
Xen and VMware currently support a special multi-call
hypercall feature [4][20].
An important difference between multi-calls and
exception-less system calls is the level of flexibility ex-
posed. The multi-call proposals do not investigate the
possibility of parallel execution of system calls, or ad-
dress the issue of blocking system calls. In multi-calls,
system calls are executed sequentially; each system call
must complete before a subsequent can be issued. With
exception-less system calls, system calls can be executed
in parallel, and in the presence of blocking, the next call
can execute immediately.
6.2 Locality of Execution and Multicores
Several researchers have studied the effects of operating
system execution on application performance [1, 3, 7, 6,
11, 13]. Larus and Parkes also identified processor inef-
USENIX Association
USENIX Association
ficiencies of server workloads, although not focusing on
the interaction with the operating system. They proposed
Cohort Scheduling to efficiently execute staged computa-
tions to improve locality of execution [11].
Techniques such as Soft Timers [3] and Lazy Receiver
Processing [9] also address the issue of locality of execu-
tion, from the other side of the compute stack: handling
device interrupts. Both techniques describe how to limit
processor interference associated with interrupt handling,
while not impacting the latency of servicing requests.
Most similar to the multicore execution of FlexSC
is Computation Spreading proposed by Chakraborty et.
al [6]. They introduced processor modifications to al-
low for hardware migration of threads, and evaluated the
effects on migrating threads upon entering the kernel to
specialize cores. Their simulation-based results show an
improvement of up to 20% on Apache, however, they ex-
plicitly do not model TLBs and provide for fast thread mi-
gration between cores. On current hardware, synchronous
thread migration between cores requires a costly inter-
processor interrupt.
Recently, both Corey and Factored Operating System
(fos) have proposed dedicating cores for specific operating
system functionality [24, 25]. There are two main differ-
ences between the core specialization possible with these
proposals and FlexSC. First, both Corey and fos require
a micro-kernel design of the operating system kernel in
order to execute specific kernel functionality on dedicated
cores. Second, FlexSC can dynamically adapt the propor-
tion of cores used by the kernel, or cores shared by user
and kernel execution, depending on the current workload
behavior.
Explicit off-loading of select OS functionality to cores
has also been studied for performance [15, 16] and power
reduction in the presence of single-ISA heterogeneous
multicores [14]. While these proposals rely on expen-
sive inter-processor interrupts to offload system calls, we
hope FlexSC can provide for a more efficient, and flexible,
mechanism that can be used by such proposals.
6.3. Non-blocking Execution
Past research on improving system call performance has
focused extensively on blocking versus non-blocking be-
havior. Typically researchers have analyzed the use of
threading, event-based (non-blocking), and hybrid sys-
tems for achieving high performance on server applica-
tions [2, 10, 17, 21, 22, 23]. Capriccio described tech-
niques to improve performance of user-level thread li-
braries for server applications [22]. Specifically, Behren
et al. showed how to efficiently manage thread stacks,
minimizing wasted space, and propose resource aware
scheduling to improver server performance. For an
extensive performance comparison of thread-based and
event-driven Web server architectures we refer the reader
to [17].
Finally, the Linux community has proposed a generic
mechanism for implementing non-blocking system calls,
which is call asynchronous system calls [5]. In their pro-
posal, system calls are still exception-based, and tenta-
tively execute synchronously. Like scheduler activations,
if a blocking condition is detected, they utilize a “syslet”
thread to block, allowing the user thread to continue exe-
cution.
The main difference between many of the proposals for
non-blocking execution and FlexSC is that none of the
non-blocking system call proposals completely decouple
the invocation of the system call from its execution. As
we have discussed, the flexibility resulting from this de-
coupling is crucial for efficiently exploring optimizations
such as system call batching and core specialization.
7 Concluding Remarks
In this paper, we introduced the concept of exception-less
system calls that decouples system call invocation from
execution. This allows for flexible scheduling of system
call execution which in turn enables system call batching
and dynamic core specialization that both improve local-
ity in a significant way. System calls are issued by writ-
ing kernel requests to a reserved syscall page using nor-
mal store operations, and they are executed by special in-
kernel syscall threads, which then post the results to the
syscall page.
In fact, the concept of exception-less system calls origi-
nated as a mechanism for low-latency communication be-
tween user and kernel-space with hyper-threaded proces-
sors in mind. We had hoped that communicating directly
through the shared L1 cache would be much more effi-
cient than mode switching. However, the measurements
presented in Section 2 made it clear that mixing user and
kernel-mode execution on the same core would not be effi-
cient for server class workloads. In future work we intend
to study how to exploit exception-less system calls as a
communication mechanism in hyper-threaded processors.
We presented our implementation of FlexSC, a Linux
kernel extension, and FlexSC-Threads, a 1/-on-N thread-
ing package that is binary compatible with NPTL and
that transparently transforms synchronous system calls
into exception-less ones. With this implementation,
we demonstrated how FlexSC improves throughput of
Apache by up to 116% and MySQL by up to 40% while
requiring no modifications to the applications. We be-
lieve these two workloads are representative of other
highly threaded server workloads that would benefit from
FlexSC. For example, experiments with the BIND DNS
server demonstrated throughput improvements of between
30% and 105% depending on the concurrency of requests.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) 45
46
In the current implementation of FlexSC, syscall
threads process system call requests in no specific or-
der, opportunistically issuing calls as they are posted on
syscall pages. The asynchronous execution model, how-
ever, would allow for different selection algorithms. For
example, syscall threads could sort the requests to con-
secutively execute requests of the same type, potentially
yielding greater locality of execution. Also, system calls
that perform I/O could be prioritized so as to issue them
as early as possible. Finally, if a large number of cores are
available, cores could be dedicated to specific system call
types to promote further locality gains.
8 Acknowledgements
This work was supported in part by Discovery Grant fund-
ing from the Natural Sciences and Engineering Research
Council (NSERC) of Canada. We would like to thank
the feedback from the OSDI reviewers, and to Emmett
Witchel for shepherding our paper. Special thanks to
Ioana Burcea for encouraging the work in its early stages,
and the Computer Systems Lab members (University of
Toronto), as well as Benjamin Gamsa, for insightful com-
ments on the work and drafts of this paper.
References
[1] AGARWAL, A., HENNESSY, J., AND HOROWITZ, M. Cache per-
formance of operating system and multiprogramming workloads.
ACM Trans. Comput. Syst. 6, 4 (1988), 393-431.
ANDERSON, T. E., BERSHAD, B. N., LAZOWSKA, E. D., AND
LEvy, H. M. Scheduler Activations: Effective Kernel Support for
the User-Level Management of Parallelism. ACM Trans. Comput.
Syst. 10, 1 (1992), 53-79.
[3] ARON, M., AND DRUSCHEL, P. Soft timers: efficient microsec-
ond software timer support for network processing. ACM Trans.
Comput. Syst. (TOCS) 18, 3 (2000), 197-228.
BARHAM, P., DRAGOVIC, B., FRASER, K., HAND, S., HAR-
RIS, T., Ho, A., NEUGEBAUER, R., PRATT, I., AND WARFIELD,
A. Xen and the art of virtualization. In Proceedings of the 19th
ACM Symposium on Operating Systems Principles (SOSP) (2003),
pp. 164-177.
[5] BROWN, Z. Asynchronous system calls. In Proceedings of the
Ottawa Linux Symposium (OLS) (2007), pp. 81-85.
[6] CHAKRABORTY, K., WELLS, P. M., AND SOHI, G. S. Com-
putation Spreading: Employing Hardware Migration to Specialize
CMP Cores On-the-fly. In Proceedings of the 12th International
Conference on Architectural Support for Programming Languages
and Operating Systems (ASPLOS) (2006), pp. 283-292.
[7] CHEN, J. B., AND BERSHAD, B. N. The impact of operating
system structure on memory system performance. In Proceed-
ings of the 14th ACM Symposium on Operating Systems Principles
(SOSP) (1993), pp. 120-133.
DREPPER, U., AND MOLNAR, I. The Native POSIX
Thread Library for Linux. Tech. rep., RedHat Inc, 2003.
http://people.redhat.com/drepper/nptl-design.pdf.
[2
ey
[4
i
[8
=
[9
—
DRUSCHEL, P., AND BANGA, G. Lazy receiver processing (LRP):
a network subsystem architecture for server systems. In Proceed-
ings of the 2nd USENIX Symposium on Operating Systems Design
and Implementation (OSDI) (1996), pp. 261-275.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
[10]
[11]
[12]
[13]
[14]
[15]
[16]
[17]
[18]
[19]
[20]
[21]
[22]
[23]
[24]
[25]
ELMELEEGY, K., CHANDA, A., Cox, A. L., AND
ZWAENEPOEL, W._ Lazy asynchronous I/O for event-driven
servers. In Proceedings of the annual conference on USENIX
Annual Technical Conference (ATEC) (2004), pp. 21-21.
LARUS, J., AND PARKES, M. Using Cohort-Scheduling to En-
hance Server Performance. In Proceedings of the annual con-
ference on USENIX Annual Technical Conference (ATEC) (2002),
pp. 103-114.
LI, T., JOHN, L. K., SIVASUBRAMANIAM, A., VIJAYKRISH-
NAN, N., AND RUBIO, J. Understanding and Improving Operating
System Effects in Control Flow Prediction. In Proceedings of the
10th International Conference on Architectural Support for Pro-
gramming Languages and Operating Systems (ASPLOS) (2002),
pp. 68-80.
MOGUL, J. C., AND BorG, A. The Effect of Context Switches
on Cache Performance. In Proceedings of the 4th International
Conference on Architectural Support for Programming Languages
and Operating Systems (ASPLOS) (1991), pp. 75-84.
MOGUL, J. C., MUDIGONDA, J., BINKERT, N., RAN-
GANATHAN, P., AND TALWAR, V. Using asymmetric single-ISA
CMPs to save energy on operating systems. IEEE Micro 28, 3
(2008), 26-41.
NELLANS, D., BALASUBRAMONIAN, R., AND BRUNVAND,
E. OS execution on multi-cores: is out-sourcing worthwhile?
SIGOPS Oper. Syst. Rev. 43, 2 (2009), 104-105.
NELLANS, D., SUDAN, K., BRUNVAND, E., AND BALASUBRA-
MONIAN, R. Improving Server Performance on Multi-Cores via
Selective Off-loading of OS Functionality. In Sixth Annual Work-
shorp on the Interaction between Operating Systems and Com-
puter Architecture (WIOSCA) (2010), pp. 13-20.
PARIAG, D., BRECHT, T., HARJI, A., BUHR, P., SHUKLA, A.,
AND CHERITON, D. R. Comparing the performance of Web
server architectures. In Proceedings of the 2nd European Con-
ference on Computer Systems (Eurosys) (2007), pp. 231-243.
RAJAGOPALAN, M., DEBRAY, S. K., HILTUNEN, M. A., AND
SCHLICHTING, R. D. Cassyopia: compiler assisted system opti-
mization. In Proceedings of the 9th conference on Hot Topics in
Operating Systems (HotOS) (2003), pp. 18-18.
REDSTONE, J. A., EGGERS, S. J., AND LEVY, H. M. An analysis
of operating system behavior on a simultaneous multithreaded ar-
chitecture. In Proceedings of the 9th International Conference on
Architectural Support for Programming Languages and Operating
Systems (ASPLOS) (2000), pp. 245-256.
VMWaARE. VMWare Virtual Machine Interface Specification.
http://www.vmware.com/pdf/vmi_specs.pdf.
VON BEHREN, R., CONDIT, J., AND BREWER, E. Why Events
Are A Bad Idea (for high-concurrency servers). In Proceedings of
the 9th conference on Hot Topics in Operating Systems (HotOS)
(2003).
VON BEHREN, R., CONDIT, J., ZHOU, F., NECULA, G. C., AND
BREWER, E. Capriccio: scalable threads for internet services. In
Proceedings of the 19th ACM Symposium on Operating Systems
Principles (SOSP) (2003), pp. 268-281.
WELSH, M., CULLER, D., AND BREWER, E. SEDA: An Ar-
chitecture for Well-Conditioned, Scalable Internet Services. In
Proceedings of the 18th ACM Symposium on Operating Systems
Principles (SOSP) (2001), pp. 230-243.
WENTZLAFF, D., AND AGARWAL, A. Factored Operating Sys-
tems (fos): The Case for a Scalable Operating System for Multi-
cores. SIGOPS Oper. Syst. Rev. 43, 2 (2009), 76-85.
WICKIZER, S. B., CHEN, H., CHEN, R., MAO, Y., KAASHOEK,
F., MORRIS, R., PESTEREV, A., STEIN, L., WU, M., DAI, Y.,
ZHANG, Y., AND ZHANG, Z. Corey: An operating system for
many cores. In Proceedings of the 8th USENIX Symposium on
Operating Systems Design and Implementation (OSDI) (2008).
USENIX Association
USENIX Association
Finding a needle in Haystack: Facebook’s photo storage
Doug Beaver, Sanjeev Kumar, Harry C. Li, Jason Sobel, Peter Vajgel,
Facebook Inc.
{doug, skumar, hcli, jsobel, pv}@facebook.com
Abstract: This paper describes Haystack, an object stor-
age system optimized for Facebook’s Photos applica-
tion. Facebook currently stores over 260 billion images,
which translates to over 20 petabytes of data. Users up-
load one billion new photos (~60 terabytes) each week
and Facebook serves over one million images per sec-
ond at peak. Haystack provides a less expensive and
higher performing solution than our previous approach,
which leveraged network attached storage appliances
over NFS. Our key observation is that this traditional
design incurs an excessive number of disk operations
because of metadata lookups. We carefully reduce this
per photo metadata so that Haystack storage machines
can perform all metadata lookups in main memory. This
choice conserves disk operations for reading actual data
and thus increases overall throughput.
1. Introduction
Sharing photos is one of Facebook’s most popular fea-
tures. To date, users have uploaded over 65 billion pho-
tos making Facebook the biggest photo sharing website
in the world. For each uploaded photo, Facebook gen-
erates and stores four images of different sizes, which
translates to over 260 billion images and more than 20
petabytes of data. Users upload one billion new photos
(~60 terabytes) each week and Facebook serves over
one million images per second at peak. As we expect
these numbers to increase in the future, photo storage
poses a significant challenge for Facebook’s infrastruc-
ture.
This paper presents the design and implementation
of Haystack, Facebook’s photo storage system that has
been in production for the past 24 months. Haystack is
an object store [7, 10, 12, 13, 25, 26] that we designed
for sharing photos on Facebook where data is written
once, read often, never modified, and rarely deleted. We
engineered our own storage system for photos because
traditional filesystems perform poorly under our work-
load.
In our experience, we find that the disadvantages of
a traditional POSIX [21] based filesystem are directo-
ries and per file metadata. For the Photos application
most of this metadata, such as permissions, is unused
and thereby wastes storage capacity. Yet the more sig-
nificant cost is that the file’s metadata must be read from
disk into memory in order to find the file itself. While
insignificant on a small scale, multiplied over billions
of photos and petabytes of data, accessing metadata is
the throughput bottleneck. We found this to be our key
problem in using a network attached storage (NAS) ap-
pliance mounted over NFS. Several disk operations were
necessary to read a single photo: one (or typically more)
to translate the filename to an inode number, another to
read the inode from disk, and a final one to read the
file itself. In short, using disk IOs for metadata was the
limiting factor for our read throughput. Observe that in
practice this problem introduces an additional cost as we
have to rely on content delivery networks (CDNs), such
as Akamai [2], to serve the majority of read traffic.
Given the disadvantages of a traditional approach,
we designed Haystack to achieve four main goals:
High throughput and low latency. Our photo storage
systems have to keep up with the requests users make.
Requests that exceed our processing capacity are either
ignored, which is unacceptable for user experience, or
handled by a CDN, which is expensive and reaches a
point of diminishing returns. Moreover, photos should
be served quickly to facilitate a good user experience.
Haystack achieves high throughput and low latency
by requiring at most one disk operation per read. We
accomplish this by keeping all metadata in main mem-
ory, which we make practical by dramatically reducing
the per photo metadata necessary to find a photo on disk.
Fault-tolerant. In large scale systems, failures happen
every day. Our users rely on their photos being available
and should not experience errors despite the inevitable
server crashes and hard drive failures. It may happen
that an entire datacenter loses power or a cross-country
link is severed. Haystack replicates each photo in
geographically distinct locations. If we lose a machine
we introduce another one to take its place, copying data
for redundancy as necessary.
Cost-effective. Haystack performs better and is less
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) 47
48
expensive than our previous NFS-based approach. We
quantify our savings along two dimensions: Haystack’s
cost per terabyte of usable storage and Haystack’s read
rate normalized for each terabyte of usable storage!.
In Haystack, each usable terabyte costs ~28% less
and processes ~4x more reads per second than an
equivalent terabyte on a NAS appliance.
Simple. In a production environment we cannot over-
state the strength of a design that is straight-forward
to implement and to maintain. As Haystack is a new
system, lacking years of production-level testing, we
paid particular attention to keeping it simple. That
simplicity let us build and deploy a working system in a
few months instead of a few years.
This work describes our experience with Haystack
from conception to implementation of a production
quality system serving billions of images a day. Our
three main contributions are:
e Haystack, an object storage system optimized for
the efficient storage and retrieval of billions of pho-
tos.
e Lessons learned in building and scaling an inex-
pensive, reliable, and available photo storage sys-
tem.
e A characterization of the requests made to Face-
book’s photo sharing application.
We organize the remainder of this paper as fol-
lows. Section 2 provides background and highlights
the challenges in our previous architecture. We de-
scribe Haystack’s design and implementation in Sec-
tion 3. Section 4 characterizes our photo read and write
workload and demonstrates that Haystack meets our de-
sign goals. We draw comparisons to related work in Sec-
tion 5 and conclude this paper in Section 6.
2 Background & Previous Design
In this section, we describe the architecture that ex-
isted before Haystack and highlight the major lessons
we learned. Because of space constraints our discus-
sion of this previous design elides several details of a
production-level deployment.
2.1 Background
We begin with a brief overview of the typical design
for how web servers, content delivery networks (CDNs),
and storage systems interact to serve photos on a popular
'The term ‘usable’ takes into account capacity consumed by fac-
tors such as RAID level, replication, and the underlying filesystem
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
Web
Server Photo
Photo
Storage
Photo
Storage
Storage
3
—__—__»>
Browser |
6
a
ho
Figure 1: Typical Design
site. Figure | depicts the steps from the moment when
a user visits a page containing an image until she down-
loads that image from its location on disk. When visiting
a page the user’s browser first sends an HTTP request
to a web server which is responsible for generating the
markup for the browser to render. For each image the
web server constructs a URL directing the browser to a
location from which to download the data. For popular
sites this URL often points to a CDN. If the CDN has
the image cached then the CDN responds immediately
with the data. Otherwise, the CDN examines the URL,
which has enough information embedded to retrieve the
photo from the site’s storage systems. The CDN then
updates its cached data and sends the image to the user’s
browser.
2.2 NFS-based Design
In our first design we implemented the photo storage
system using an NFS-based approach. While the rest
of this subsection provides more detail on that design,
the major lesson we learned is that CDNs by themselves
do not offer a practical solution to serving photos on a
social networking site. CDNs do effectively serve the
hottest photos— profile pictures and photos that have
been recently uploaded—but a social networking site
like Facebook also generates a large number of requests
for less popular (often older) content, which we refer to
as the long tail. Requests from the long tail account for a
significant amount of our traffic, almost all of which ac-
cesses the backing photo storage hosts as these requests
typically miss in the CDN. While it would be very con-
venient to cache all of the photos for this long tail, doing
so would not be cost effective because of the very large
cache sizes required.
Our NFS-based design stores each photo in its own
file on a set of commercial NAS appliances. A set of
USENIX Association
USENIX Association
Web Cc > RC
\ NFS Jf
Va" a
Photo Store Photo Store
Server Server
a //
3
or
Browser | <————_
8
Figure 2: NFS-based Design
machines, Photo Store servers, then mount all the vol-
umes exported by these NAS appliances over NFS. Fig-
ure 2 illustrates this architecture and shows Photo Store
servers processing HTTP requests for images. From an
image’s URL a Photo Store server extracts the volume
and full path to the file, reads the data over NFS, and
returns the result to the CDN.
We initially stored thousands of files in each directory
of an NFS volume which led to an excessive number of
disk operations to read even a single image. Because
of how the NAS appliances manage directory metadata,
placing thousands of files in a directory was extremely
inefficient as the directory’s blockmap was too large to
be cached effectively by the appliance. Consequently
it was common to incur more than 10 disk operations to
retrieve a single image. After reducing directory sizes to
hundreds of images per directory, the resulting system
would still generally incur 3 disk operations to fetch an
image: one to read the directory metadata into memory,
a second to load the inode into memory, and a third to
read the file contents.
To further reduce disk operations we let the Photo
Store servers explicitly cache file handles returned by
the NAS appliances. When reading a file for the first
time a Photo Store server opens a file normally but also
caches the filename to file handle mapping in mem-
cache [18]. When requesting a file whose file handle
is cached, a Photo Store server opens the file directly
using a custom system call, open_by_filehandle, that
we added to the kernel. Regrettably, this file handle
cache provides only a minor improvement as less pop-
ular photos are less likely to be cached to begin with.
One could argue that an approach in which all file han-
dles are stored in memcache might be a workable solu-
tion. However, that only addresses part of the problem
as it relies on the NAS appliance having all of its in-
odes in main memory, an expensive requirement for tra-
ditional filesystems. The major lesson we learned from
the NAS approach is that focusing only on caching—
whether the NAS appliance’s cache or an external cache
like memcache—has limited impact for reducing disk
operations. The storage system ends up processing the
long tail of requests for less popular photos, which are
not available in the CDN and are thus likely to miss in
our caches.
2.3 Discussion
It would be difficult for us to offer precise guidelines
for when or when not to build a custom storage system.
However, we believe it still helpful for the community
to gain insight into why we decided to build Haystack.
Faced with the bottlenecks in our NFS-based design,
we explored whether it would be useful to build a sys-
tem similar to GFS [9]. Since we store most of our user
data in MySQL databases, the main use cases for files
in our system were the directories engineers use for de-
velopment work, log data, and photos. NAS appliances
offer a very good price/performance point for develop-
ment work and for log data. Furthermore, we leverage
Hadoop [11] for the extremely large log data. Serving
photo requests in the long tail represents a problem for
which neither MySQL, NAS appliances, nor Hadoop are
well-suited.
One could phrase the dilemma we faced as exist-
ing storage systems lacked the right RAM-to-disk ra-
tio. However, there is no right ratio. The system just
needs enough main memory so that all of the filesystem
metadata can be cached at once. In our NAS-based ap-
proach, one photo corresponds to one file and each file
requires at least one inode, which is hundreds of bytes
large. Having enough main memory in this approach is
not cost-effective. To achieve a better price/performance
point, we decided to build a custom storage system that
reduces the amount of filesystem metadata per photo so
that having enough main memory is dramatically more
cost-effective than buying more NAS appliances.
3 Design & Implementation
Facebook uses a CDN to serve popular images and
leverages Haystack to respond to photo requests in the
long tail efficiently. When a web site has an I/O bot-
tleneck serving static content the traditional solution is
to use a CDN. The CDN shoulders enough of the bur-
den so that the storage system can process the remaining
tail. At Facebook a CDN would have to cache an unrea-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) 49
50
sonably large amount of the static content in order for
traditional (and inexpensive) storage approaches not to
be I/O bound.
Understanding that in the near future CDNs would not
fully solve our problems, we designed Haystack to ad-
dress the critical bottleneck in our NFS-based approach:
disk operations. We accept that requests for less popu-
lar photos may require disk operations, but aim to limit
the number of such operations to only the ones neces-
sary for reading actual photo data. Haystack achieves
this goal by dramatically reducing the memory used for
filesystem metadata, thereby making it practical to keep
all this metadata in main memory.
Recall that storing a single photo per file resulted
in more filesystem metadata than could be reasonably
cached. Haystack takes a straight-forward approach:
it stores multiple photos in a single file and therefore
maintains very large files. We show that this straight-
forward approach is remarkably effective. Moreover, we
argue that its simplicity is its strength, facilitating rapid
implementation and deployment. We now discuss how
this core technique and the architectural components
surrounding it provide a reliable and available storage
system. In the following description of Haystack, we
distinguish between two kinds of metadata. Applica-
tion metadata describes the information needed to con-
struct a URL that a browser can use to retrieve a photo.
Filesystem metadata identifies the data necessary for a
host to retrieve the photos that reside on that host’s disk.
3.1. Overview
The Haystack architecture consists of 3 core compo-
nents: the Haystack Store, Haystack Directory, and
Haystack Cache. For brevity we refer to these com-
ponents with ‘Haystack’ elided. The Store encapsu-
lates the persistent storage system for photos and is the
only component that manages the filesystem metadata
for photos. We organize the Store’s capacity by phys-
ical volumes. For example, we can organize a server’s
10 terabytes of capacity into 100 physical volumes each
of which provides 100 gigabytes of storage. We further
group physical volumes on different machines into logi-
cal volumes. When Haystack stores a photo on a logical
volume, the photo is written to all corresponding physi-
cal volumes. This redundancy allows us to mitigate data
loss due to hard drive failures, disk controller bugs, etc.
The Directory maintains the logical to physical mapping
along with other application metadata, such as the log-
ical volume where each photo resides and the logical
volumes with free space. The Cache functions as our in-
ternal CDN, which shelters the Store from requests for
the most popular photos and provides insulation if up-
stream CDN nodes fail and need to refetch content.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
See eee
1 I
Haystack |
Store |
}
f I
Haystack
Directory
2) 13
O05. |
ac Haystack | o'"05
Cache ! '
PAa900°
Figure 3: Serving a photo
Figure 3 illustrates how the Store, Directory, and
Cache components fit into the canonical interactions be-
tween a user’s browser, web server, CDN, and storage
system. In the Haystack architecture the browser can be
directed to either the CDN or the Cache. Note that while
the Cache is essentially a CDN, to avoid confusion we
use “CDN’ to refer to external systems and ‘Cache’ to
refer to our internal one that caches photos. Having an
internal caching infrastructure gives us the ability to re-
duce our dependence on external CDNs.
When a user visits a page the web server uses the Di-
rectory to construct a URL for each photo. The URL
contains several pieces of information, each piece cor-
responding to the sequence of steps from when a user’s
browser contacts the CDN (or Cache) to ultimately re-
trieving a photo from a machine in the Store. A typical
URL that directs the browser to the CDN looks like the
following:
http://(CDN)/(Cache)/(Machine id)/(Logical volume, Photo)
The first part of the URL specifies from which CDN
to request the photo. The CDN can lookup the photo
internally using only the last part of the URL: the logical
volume and the photo id. If the CDN cannot locate the
photo then it strips the CDN address from the URL and
contacts the Cache. The Cache does a similar lookup to
find the photo and, on a miss, strips the Cache address
from the URL and requests the photo from the specified
Store machine. Photo requests that go directly to the
Cache have a similar workflow except that the URL is
missing the CDN specific information.
USENIX Association
USENIX Association
pasa Sooo ole eee ee
' |
Haystack
Store |
1
' |
Haystack
Directory
Browser
Figure 4: Uploading a photo
Figure 4 illustrates the upload path in Haystack.
When a user uploads a photo she first sends the data to a
web server. Next, that server requests a write-enabled
logical volume from the Directory. Finally, the web
server assigns a unique id to the photo and uploads it
to each of the physical volumes mapped to the assigned
logical volume.
3.2 Haystack Directory
The Directory serves four main functions. First, it pro-
vides a mapping from logical volumes to physical vol-
umes. Web servers use this mapping when uploading
photos and also when constructing the image URLs for
a page request. Second, the Directory load balances
writes across logical volumes and reads across physi-
cal volumes. Third, the Directory determines whether
a photo request should be handled by the CDN or by
the Cache. This functionality lets us adjust our depen-
dence on CDNs. Fourth, the Directory identifies those
logical volumes that are read-only either because of op-
erational reasons or because those volumes have reached
their storage capacity. We mark volumes as read-only at
the granularity of machines for operational ease.
When we increase the capacity of the Store by adding
new machines, those machines are write-enabled; only
write-enabled machines receive uploads. Over time the
available capacity on these machines decreases. When a
machine exhausts its capacity, we mark it as read-only.
In the next subsection we discuss how this distinction
has subtle consequences for the Cache and Store.
The Directory is a relatively straight-forward compo-
nent that stores its information in a replicated database
accessed via a PHP interface that leverages memcache
to reduce latency. In the event that we lose the data on
a Store machine we remove the corresponding entry in
the mapping and replace it when a new Store machine is
brought online.
3.3. Haystack Cache
The Cache receives HTTP requests for photos from
CDNs and also directly from users’ browsers. We or-
ganize the Cache as a distributed hash table and use a
photo’s id as the key to locate cached data. If the Cache
cannot immediately respond to the request, then the
Cache fetches the photo from the Store machine iden-
tified in the URL and replies to either the CDN or the
user’s browser as appropriate.
We now highlight an important behavioral aspect of
the Cache. It caches a photo only if two conditions
are met: (a) the request comes directly from a user and
not the CDN and (b) the photo is fetched from a write-
enabled Store machine. The justification for the first
condition is that our experience with the NFS-based de-
sign showed post-CDN caching is ineffective as it is un-
likely that a request that misses in the CDN would hit in
our internal cache. The reasoning for the second is in-
direct. We use the Cache to shelter write-enabled Store
machines from reads because of two interesting proper-
ties: photos are most heavily accessed soon after they
are uploaded and filesystems for our workload gener-
ally perform better when doing either reads or writes
but not both (Section 4.1). Thus the write-enabled Store
machines would see the most reads if it were not for
the Cache. Given this characteristic, an optimization we
plan to implement is to proactively push recently up-
loaded photos into the Cache as we expect those photos
to be read soon and often.
3.4 Haystack Store
The interface to Store machines is intentionally basic.
Reads make very specific and well-contained requests
asking for a photo with a given id, for a certain logical
volume, and from a particular physical Store machine.
The machine returns the photo if it is found. Otherwise,
the machine returns an error.
Each Store machine manages multiple physical vol-
umes. Each volume holds millions of photos. For
concreteness, the reader can think of a physical vol-
ume as simply a very large file (100 GB) saved as
‘/hay/haystack_<logical volume id>’. A Store machine
can access a photo quickly using only the id of the cor-
responding logical volume and the file offset at which
the photo resides. This knowledge is the keystone of
the Haystack design: retrieving the filename, offset, and
size for a particular photo without needing disk opera-
tions. A Store machine keeps open file descriptors for
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) 51
52
Sea Header Magic Number
Cookie
Needle 1 Key
Alternate Key
Flags
Needle 2 Size
Data
Needle 3
Figure 5: Layout of Haystack Store file
Footer Magic Number
Data Checksum
Padding
Field Explanation
Header Magic number used for recovery
Cookie Random number to mitigate
brute force lookups
Key 64-bit photo id
Alternate key 32-bit supplemental id
Flags Signifies deleted status
Size Data size
Data The actual photo data
Footer Magic number for recovery
Data Checksum | Used to check integrity
Padding Total needle size is aligned to 8 bytes
Table 1: Explanation of fields in a needle
each physical volume that it manages and also an in-
memory mapping of photo ids to the filesystem meta-
data (i.e., file, offset and size in bytes) critical for re-
trieving that photo.
We now describe the layout of each physical volume
and how to derive the in-memory mapping from that
volume. A Store machine represents a physical volume
as a large file consisting of a superblock followed by
a sequence of needles. Each needle represents a photo
stored in Haystack. Figure 5 illustrates a volume file and
the format of each needle. Table | describes the fields
in each needle.
To retrieve needles quickly, each Store machine main-
tains an in-memory data structure for each of its vol-
umes. That data structure maps pairs of (key, alter-
nate key)* to the corresponding needle’s flags, size in
?For historical reasons, a photo’s id corresponds to the key while its
type is used for the alternate key. During an upload, web servers scale
each photo to four different sizes (or types) and store them as separate
needles, but with the same key. The important distinction among these
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
bytes, and volume offset. After a crash, a Store machine
can reconstruct this mapping directly from the volume
file before processing requests. We now describe how
a Store machine maintains its volumes and in-memory
mapping while responding to read, write, and delete re-
quests (the only operations supported by the Store).
3.4.1. Photo Read
When a Cache machine requests a photo it supplies the
logical volume id, key, alternate key, and cookie to the
Store machine. The cookie is a number embedded in
the URL for a photo. The cookie’s value is randomly
assigned by and stored in the Directory at the time that
the photo is uploaded. The cookie effectively eliminates
attacks aimed at guessing valid URLs for photos.
When a Store machine receives a photo request from a
Cache machine, the Store machine looks up the relevant
metadata in its in-memory mappings. If the photo has
not been deleted the Store machine seeks to the appro-
priate offset in the volume file, reads the entire needle
from disk (whose size it can calculate ahead of time),
and verifies the cookie and the integrity of the data. If
these checks pass then the Store machine returns the
photo to the Cache machine.
3.4.2. Photo Write
When uploading a photo into Haystack web servers pro-
vide the logical volume id, key, alternate key, cookie,
and data to Store machines. Each machine syn-
chronously appends needle images to its physical vol-
ume files and updates in-memory mappings as needed.
While simple, this append-only restriction complicates
some operations that modify photos, such as rotations.
As Haystack disallows overwriting needles, photos can
only be modified by adding an updated needle with the
same key and alternate key. If the new needle is written
to a different logical volume than the original, the Direc-
tory updates its application metadata and future requests
will never fetch the older version. If the new needle is
written to the same logical volume, then Store machines
append the new needle to the same corresponding physi-
cal volumes. Haystack distinguishes such duplicate nee-
dles based on their offsets. That is, the latest version of a
needle within a physical volume is the one at the highest
offset.
3.4.3 Photo Delete
Deleting a photo is straight-forward. A Store machine
sets the delete flag in both the in-memory mapping
and synchronously in the volume file. Requests to get
deleted photos first check the in-memory flag and return
errors if that flag is enabled. Note that the space occu-
needles is the alternate key field, which in decreasing order can be ‘n,’
‘a, ‘s, or ‘t’.
USENIX Association
USENIX Association
Superblock
Needle 1 Key
Needle 2 Alternate Key
Flags
Needle 3 Oia
Size
Figure 6: Layout of Haystack Index file
pied by deleted needles is for the moment lost. Later,
we discuss how to reclaim deleted needle space by com-
pacting volume files.
3.4.4 The Index File
Store machines use an important optimization—the in-
dex file—when rebooting. While in theory a machine
can reconstruct its in-memory mappings by reading all
of its physical volumes, doing so is time-consuming as
the amount of data (terabytes worth) has to all be read
from disk. Index files allow a Store machine to build its
in-memory mappings quickly, shortening restart time.
Store machines maintain an index file for each of
their volumes. The index file is a checkpoint of the in-
memory data structures used to locate needles efficiently
on disk. An index file’s layout is similar to a volume
file’s, containing a superblock followed by a sequence
of index records corresponding to each needle in the su-
perblock. These records must appear in the same order
as the corresponding needles appear in the volume file.
Figure 6 illustrates the layout of the index file and Ta-
ble 2 explains the different fields in each record.
Restarting using the index is slightly more compli-
cated than just reading the indices and initializing the
in-memory mappings. The complications arise because
index files are updated asynchronously, meaning that
index files may represent stale checkpoints. When we
write a new photo the Store machine synchronously ap-
pends a needle to the end of the volume file and asyn-
chronously appends a record to the index file. When
we delete a photo, the Store machine synchronously sets
the flag in that photo’s needle without updating the in-
dex file. These design decisions allow write and delete
operations to return faster because they avoid additional
synchronous disk writes. They also cause two side ef-
fects we must address: needles can exist without corre-
sponding index records and index records do not reflect
deleted photos.
Field | Explanation
Key 64-bit key
Alternate key | 32-bit alternate key
Flags Currently unused
Offset Needle offset in the Haystack Store
Size Needle data size
Table 2: Explanation of fields in index file.
We refer to needles without corresponding index
records as orphans. During restarts, a Store machine
sequentially examines each orphan, creates a match-
ing index record, and appends that record to the index
file. Note that we can quickly identify orphans because
the last record in the index file corresponds to the last
non-orphan needle in the volume file. To complete the
restart, the Store machine now initializes its in-memory
mappings using only the index files.
Since index records do not reflect deleted photos, a
Store machine may retrieve a photo that has in fact been
deleted. To address this issue, after a Store machine
reads the entire needle for a photo, that machine can
then inspect the deleted flag. If a needle is marked as
deleted the Store machine updates its in-memory map-
ping accordingly and notifies the Cache that the object
was not found.
3.4.5 Filesystem
We describe Haystack as an object store that utilizes
a generic Unix-like filesystem, but some filesystems
are better suited for Haystack than others. In partic-
ular, the Store machines should use a filesystem that
does not need much memory to be able to perform ran-
dom seeks within a large file quickly. Currently, each
Store machine uses XFS [24], an extent based file sys-
tem. XFS has two main advantages for Haystack. First,
the blockmaps for several contiguous large files can
be small enough to be stored in main memory. Sec-
ond, XFS provides efficient file preallocation, mitigat-
ing fragmentation and reining in how large block maps
can grow.
Using XFS, Haystack can eliminate disk operations
for retrieving filesystem metadata when reading a photo.
This benefit, however, does not imply that Haystack can
guarantee every photo read will incur exactly one disk
operation. There exists corner cases where the filesys-
tem requires more than one disk operation when photo
data crosses extents or RAID boundaries. Haystack pre-
allocates 1 gigabyte extents and uses 256 kilobyte RAID
stripe sizes so that in practice we encounter these cases
rarely.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) a3
54
3.5 Recovery from failures
Like many other large-scale systems running on com-
modity hardware [5, 4, 9], Haystack needs to tolerate
a variety of failures: faulty hard drives, misbehaving
RAID controllers, bad motherboards, etc. We use two
straight-forward techniques to tolerate failures—one for
detection and another for repair.
To proactively find Store machines that are having
problems, we maintain a background task, dubbed pitch-
fork, that periodically checks the health of each Store
machine. Pitchfork remotely tests the connection to
each Store machine, checks the availability of each vol-
ume file, and attempts to read data from the Store ma-
chine. If pitchfork determines that a Store machine con-
sistently fails these health checks then pitchfork auto-
matically marks all logical volumes that reside on that
Store machine as read-only. We manually address the
underlying cause for the failed checks offline.
Once diagnosed, we may be able to fix the prob-
lem quickly. Occasionally, the situation requires a more
heavy-handed bulk sync operation in which we reset the
data of a Store machine using the volume files supplied
by a replica. Bulk syncs happen rarely (a few each
month) and are simple albeit slow to carry out. The main
bottleneck is that the amount of data to be bulk synced is
often orders of magnitude greater than the speed of the
NIC on each Store machine, resulting in hours for mean
time to recovery. We are actively exploring techniques
to address this constraint.
3.6 Optimizations
We now discuss several optimizations important to
Haystack’s success.
3.6.1 Compaction
Compaction is an online operation that reclaims the
space used by deleted and duplicate needles (needles
with the same key and alternate key). A Store machine
compacts a volume file by copying needles into a new
file while skipping any duplicate or deleted entries. Dur-
ing compaction, deletes go to both files. Once this pro-
cedure reaches the end of the file, it blocks any further
modifications to the volume and atomically swaps the
files and in-memory structures.
We use compaction to free up space from deleted pho-
tos. The pattern for deletes is similar to photo views:
young photos are a lot more likely to be deleted. Over
the course of a year, about 25% of the photos get deleted.
3.6.2 Saving more memory
As described, a Store machine maintains an in-memory
data structure that includes flags, but our current system
only uses the flags field to mark a needle as deleted. We
eliminate the need for an in-memory representation of
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
flags by setting the offset to be 0 for deleted photos. In
addition, Store machines do not keep track of cookie
values in main memory and instead check the supplied
cookie after reading a needle from disk. Store machines
reduce their main memory footprints by 20% through
these two techniques.
Currently, Haystack uses on average 10 bytes of main
memory per photo. Recall that we scale each uploaded
image to four photos all with the same key (64 bits), dif-
ferent alternate keys (32 bits), and consequently differ-
ent data sizes (16 bits). In addition to these 32 bytes,
Haystack consumes approximately 2 bytes per image
in overheads due to hash tables, bringing the total for
four scaled photos of the same image to 40 bytes. For
comparison, consider that an xfs_inode_t structure in
Linux is 536 bytes.
3.6.3 Batch upload
Since disks are generally better at performing large se-
quential writes instead of small random writes, we batch
uploads together when possible. Fortunately, many
users upload entire albums to Facebook instead of single
pictures, providing an obvious opportunity to batch the
photos in an album together. We quantify the improve-
ment of aggregating writes together in Section 4.
4 Evaluation
We divide our evaluation into four parts. In the first we
characterize the photo requests seen by Facebook. In
the second and third we show the effectiveness of the
Directory and Cache, respectively. In the last we ana-
lyze how well the Store performs using both synthetic
and production workloads.
4.1 Characterizing photo requests
Photos are one of the primary kinds of content that users
share on Facebook. Users upload millions of photos ev-
ery day and recently uploaded photos tend to be much
more popular than older ones. Figure 7 illustrates how
popular each photo is as a function of the photo’s age.
To understand the shape of the graph, it is useful to dis-
cuss what drives Facebook’s photo requests.
4.1.1 Features that drive photo requests
Two features are responsible for 98% of Facebook’s
photo requests: News Feed and albums. The News Feed
feature shows users recent content that their friends have
shared. The album feature lets a user browse her friends’
pictures. She can view recently uploaded photos and
also browse all of the individual albums.
Figure 7 shows a sharp rise in requests for photos that
are a few days old. News Feed drives much of the traffic
for recent photos and falls sharply away around 2 days
when many stories stop being shown in the default Feed
USENIX Association
100 -
60 +
40
Cumulative % of accesses
20 +
0
0 200 400 600
Figure 7: Cumulative distribution function of the num-
ber of photos requested in a day categorized by age (time
since it was uploaded).
1 1 L 1
800 1000 1200 1400
Age (in days)
1600
Operations
Daily Counts
Photos Uploaded
~120 Million
Haystack Photos Written
~ 1.44 Billion
Photos Viewed
80-100 Billion
[ Thumbnails | 10.2 %
[ Small | 84.4 %
[ Medium ] 0.2 %
[ Large | 5.2 %
Haystack Photos Read 10 Billion
Table 3: Volume of daily photo traffic.
view. There are two key points to highl ght from the fig-
ure. First, the rapid decline in popularity suggests that
caching at both CDNs and in the Cache can be very ef-
fective for hosting popular content. Second, the graph
has a long tail implying that a significant number of re-
quests cannot be dealt with using cached data.
4.1.2 Traffic Volume
Table 3 shows the volume of photo traffic on Facebook.
The number of Haystack photos written is 12 times the
number of photos uploaded since our application scales
each image to 4 sizes and saves each size in 3 different
locations. The table shows that Haystack responds to
approximately 10% of all photo requests from CDNs.
Observe that smaller images account for most of the
photos viewed. This trait underscores our desire to min-
imize metadata overhead as inefficiencies can quickly
add up. Additionally, reading smaller images is typi-
cally a more latency sensitive operation for Facebook as
they are displayed in the News Feed whereas larger im-
USENIX Association
1200
1000
800
600
Operations per Minute
400
200 -
! 1 ! 1 1 ! !
419 4/20 4/21 4/22 4/23 4/24 4/25
Date
Figure 8: Volume of multi-write operations sent to 9
different write-enabled Haystack Store machines. The
graph has 9 different lines that closely overlap each
other.
ages are shown in albums and can be prefetched to hide
latency.
4.2 Haystack Directory
The Haystack Directory balances reads and writes
across Haystack Store machines. Figure 8 depicts that as
expected, the Directory’s straight-forward hashing pol-
icy to distribute reads and writes is very effective. The
graph shows the number of multi-write operations seen
by 9 different Store machines which were deployed into
production at the same time. Each of these boxes store a
different set of photos. Since the lines are nearly indis-
tinguishable, we conclude that the Directory balances
writes well. Comparing read traffic across Store ma-
chines shows similarly well-balanced behavior.
4.3. Haystack Cache
Figure 9 shows the hit rate for the Haystack Cache. Re-
call that the Cache only stores a photo if it is saved on
a write-enabled Store machine. These photos are rel-
atively recent, which explains the high hit rates of ap-
proximately 80%. Since the write-enabled Store ma-
chines would also see the greatest number of reads, the
Cache is effective in dramatically reducing the read re-
quest rate for the machines that would be most affected.
4.4 Haystack Store
Recall that Haystack targets the long tail of photo re-
quests and aims to maintain high-throughput and low-
latency despite seemingly random reads. We present
performance results of Store machines on both synthetic
and production workloads.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) 55
56
Reads Writes
Benchmark [Config # Operations } Thr oughput Latency (in ms) Throughput Latency (in ms)
(in images/s) Avg. Std. dev. (in images/s) Avg. Std. dev.
Random IO _ [ Only Reads ] 902.3 33.2 26.8 — — —
Haystress [A # Only Reads ] 770.6 38.9 30.2 = — —
Haystress _[ B # Only Reads ] 877.8 34.2 28.1 7 — 7
Haystress [ C # Only Multi-Writes ] — — — 6099.4 4.9 16.0
Haystress [ D # Only Multi-Writes ] — — — 7899.7 15.2 15.3
Haystress [FE # Only Multi-Writes ] = = = 10843.8 43.9 16.3
Haystress [ F # Reads & Multi-Writes ] 718.1 41.6 31.6 232.0 11.9 6.3
Haystress [G#Reads & Multi-Writes ] 692.8 42.8 33.7 440.0 11.9 6.9
Table 4: Throughput and latency of read and multi-write operations on synthetic workloads. Config B uses a mix of
8KB and 64KB images. Remaining configs use 64KB images.
100 -
60 +
Hit Rate (%)
40 +
20
1 1 i f 1 E 1
4/26 4/27 4/28 4/29 4/30 5/1 5/2
Date
Figure 9: Cache hit rate for images that might be poten-
tially stored in the Haystack Cache.
4.4.1
We deploy Store machines on commodity storage
blades. The typical hardware configuration of a 2U stor-
age blade has 2 hyper-threaded quad-core Intel Xeon
CPUs, 48 GB memory, a hardware raid controller with
256-512MB NVRAM, and 12 x 1TB SATA drives.
Each storage blade provides approximately 9TB of
capacity, configured as a RAID-6 partition managed by
the hardware RAID controller. RAID-6 provides ade-
quate redundancy and excellent read performance while
keeping storage costs down. The controller’s NVRAM
write-back cache mitigates RAID-6’s reduced write per-
formance. Since our experience suggests that caching
photos on Store machines is ineffective, we reserve the
NVRAM fully for writes. We also disable disk caches
in order to guarantee data consistency in the event of a
Experimental setup
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
crash or power loss.
4.4.2 Benchmark performance
We assess the performance of a Store machine using two
benchmarks: Randomio [22] and Haystress. Randomio
is an open-source multithreaded disk I/O program that
we use to measure the raw capabilities of storage de-
vices. It issues random 64KB reads that use direct I/O to
make sector aligned requests and reports the maximum
sustainable throughput. We use Randomio to establish a
baseline for read throughput against which we can com-
pare results from our other benchmark.
Haystress is a custom built multi-threaded program
that we use to evaluate Store machines for a variety of
synthetic workloads. It communicates with a Store ma-
chine via HTTP (as the Cache would) and assesses the
maximum read and write throughput a Store machine
can maintain. Haystress issues random reads over a
large set of dummy images to reduce the effect of the
machine’s buffer cache; that is, nearly all reads require
a disk operation. In this paper, we use seven different
Haystress workloads to evaluate Store machines.
Table 4 characterizes the read and write throughputs
and associated latencies that a Store machine can sus-
tain under our benchmarks. Workload A performs ran-
dom reads to 64KB images on a Store machine with 201
volumes. The results show that Haystack delivers 85%
of the raw throughput of the device while incurring only
17% higher latency.
We attribute a Store machine’s overhead to four fac-
tors: (a) it runs on top of the filesystem instead of access-
ing disk directly; (b) disk reads are larger than 64KB as
entire needles need to be read; (c) stored images may
not be aligned to the underlying RAID-6 device stripe
size so a small percentage of images are read from more
USENIX Association
USENIX Association
than one disk; and (d) CPU overhead of Haystack server
(index access, checksum calculations, etc.)
In workload B, we again examine a read-only work-
load but alter 70% of the reads so that they request
smaller size images (8KB instead of 64KB). In practice,
we find that most requests are not for the largest size
images (as would be shown in albums) but rather for the
thumbnails and profile pictures.
Workloads C, D, and E show a Store machine’s write
throughput. Recall that Haystack can batch writes to-
gether. Workloads C, D, and E group 1, 4, and 16 writes
into a single multi-write, respectively. The table shows
that amortizing the fixed cost of writes over 4 and 16
images improves throughput by 30% and 78% respec-
tively. As expected, this reduces per image latency, as
well.
Finally, we look at the performance in the presence
of both read and write operations. Workload F uses a
mix of 98% reads and 2% multi-writes while G uses
a mix of 96% reads and 4% multi-writes where each
multi-write writes 16 images. These ratios reflect what
is often observed in production. The table shows that the
Store delivers high read throughput even in the presence
of writes.
4.4.3. Production workload
The section examines the performance of the Store on
production machines. As noted in Section 3, there
are two classes of Stores—write-enabled and read-only.
Write-enabled hosts service read and write requests,
read-only hosts only service read requests. Since these
two classes have fairly different traffic characteristics,
we analyze a group of machines in each class. All ma-
chines have the same hardware configuration.
Viewed at a per-second granularity, there can be large
spikes in the volume of photo read and write operations
that a Store box sees. To ensure reasonable latency even
in the presence of these spikes, we conservatively allo-
cate a large number of write-enabled machines so that
their average utilization is low.
Figure 10 shows the frequency of the different types
of operations on a read-only and a write-enabled Store
machine. Note that we see peak photo uploads on Sun-
day and Monday, with a smooth drop the rest of the
week until we level out on Thursday to Saturday. Then
a new Sunday arrives and we hit a new weekly peak. In
general our footprint grows by 0.2% to 0.5% per day.
As noted in Section 3, write operations to the Store
are always multi-writes on production machines to
amortize the fixed cost of write operations. Finding
groups of images is fairly straightforward since 4 dif-
ferent sizes of each photo is stored in Haystack. It is
also common for users to upload a batch of photos into
3500 -
Read-Only Machine yee paeeeed
3000 | Multi-Writes ----++--
a 2500
Ss
£
2 2000
oO
a
no 1
5 :
2 1500 1
s ru
oO
So
1000
500 -
Q etrstietiterttetie nee neactnernealthnettarearttneetn cerca theener iterates
4/5 4/12 4/19 4/26
9000 + Write-Enabled Machine i
8000
7000
2
= 6000
=
& 5000
no
6
5 4000
g '
© 3000 F
2000
1000 7h. ...
Figure 10: Rate of different operations on two Haystack
Store machines: One read-only and the other write-
enabled.
a photo album. As a combination of these two factors,
the average number of images written per multi-write
for this write-enabled machine is 9.27.
Section 4.1.2 explained that both read and delete rates
are high for recently uploaded photos and drop over
time. This behavior can be also be observed in Fig-
ure 10; the write-enabled boxes see many more requests
(even though some of the read traffic is served by the
Cache).
Another trend worth noting: as more data gets written
to write-enabled boxes the volume of photos increases,
resulting in an increase in the read request rate.
Figure 11 shows the latency of read and multi-write
operations on the same two machines as Figure 10 over
the same period.
The latency of multi-write operations is fairly low
(between | and 2 milliseconds) and stable even as the
volume of traffic varies dramatically. Haystack ma-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) 57
58
Read [ Write-Enabled Machine ]
Multi-Write [ Write-Enabled Machine ] -------
Read [ Read-Only
Machine ] --------
Latency (ms)
hate sagan rah epty I TT Ar tthe
go wii pou i a ieee ae gh a pile el
4/5 42 4/19 4/26
Date
Figure 11: Average latency of Read and Multi-write op-
erations on the two Haystack Store machines in Fig-
ure 10 over the same 3 week period.
chines have a NVRAM-backed raid controller which
buffers writes for us. As described in Section 3, the
NVRAM allows us to write needles asynchronously and
then issue a single fsync to flush the volume file once the
multi-write is complete. Multi-write latencies are very
flat and stable.
The latency of reads on a read-only box is also fairly
stable even as the volume of traffic varies significantly
(up to 3x over the 3 week period). For a write-enabled
box the read performance is impacted by three primary
factors. First, as the number of photos stored on the ma-
chine increases, the read traffic to that machine also in-
creases (compare week-over-week traffic in figure 10).
Second, photos on write-enabled machines are cached
in the Cache while they are not cached for a read-only
machine>. This suggests that the buffer cache would be
more effective for a read-only machine. Third, recently
written photos are usually read back immediately be-
cause Facebook highlights recent content. Such reads on
Write-enabled boxes will always hit in the buffer cache
and improve the hit rate of the buffer cache. The shape
of the line in the figure is the result of a combination of
these three factors.
The CPU utilization on the Store machines is low.
CPU idle time varies between 92-96%.
5 Related Work
To our knowledge, Haystack targets a new design point
focusing on the long tail of photo requests seen by a
3Note that for traffic coming through a CDN, they are cached in
the CDNs and not in the Cache in both instances
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
large social networking website.
Filesystems Haystack takes after log-structured filesys-
tems [23] which Rosenblum and Ousterhout designed
to optimize write throughput with the idea that most
reads could be served out of cache. While measure-
ments [3] and simulations [6] have shown that log-
structured filesystems have not reached their full poten-
tial in local filesystems, the core ideas are very relevant
to Haystack. Photos are appended to physical volume
files in the Haystack Store and the Haystack Cache shel-
ters write-enabled machines from being overwhelmed
by the request rate for recently uploaded data. The key
differences are (a) that the Haystack Store machines
write their data in such a way that they can efficiently
serve reads once they become read-only and (b) the read
request rate for older data decreases over time.
Several works [8, 19, 28] have proposed how to
manage small files and metadata more efficiently. The
common thread across these contributions is how to
group related files and metadata together intelligently.
Haystack obviates these problems since it maintains
metadata in main memory and users often upload
related photos in bulk.
Object-based storage Haystack’s architecture shares
many similarities with object storage systems proposed
by Gibson et al. [10] in Network-Attached Secure Disks
(NASD). The Haystack Directory and Store are perhaps
most similar to the File and Storage Manager concepts,
respectively, in NASD that separate the logical storage
units from the physical ones. In OBFS [25], Wang et
al. build a user-level object-based filesystem that is sah
the size of XFS. Although OBFS achieves greater write
throughput than XFS, its read throughput (Haystack’s
main concern) is slightly worse.
Managing metadata Weil et al. [26, 27] address
scaling metadata management in Ceph, a petabyte-scale
object store. Ceph further decouples the mapping from
logical units to physical ones by introducing generating
functions instead of explicit mappings. Clients can cal-
culate the appropriate metadata rather than look it up.
Implementing this technique in Haystack remains future
work. Hendricks et. al [13] observe that traditional
metadata pre-fetching algorithms are less effective for
object stores because related objects, which are identi-
fied by a unique number, lack the semantic groupings
that directories implicitly impose. Their solution is to
embed inter-object relationships into the object id. This
idea is orthogonal to Haystack as Facebook explicitly
stores these semantic relationships as part of the social
USENIX Association
USENIX Association
graph. In Spyglass [15], Leung et al. propose a design
for quickly and scalably searching through metadata
of large-scale storage systems. Manber and Wu also
propose a way to search through entire filesystems in
GLIMPSE [17]. Patil et al. [20] use a sophisticated
algorithm in GIGA+ to manage the metadata associated
with billions of files per directory. We engineered a
simpler solution than many existing works as Haystack
does not have to provide search features nor traditional
UNIX filesystem semantics.
Distributed filesystems Haystack’s notion of a logi-
cal volume is similar to Lee and Thekkath’s [14] vir-
tual disks in Petal. The Boxwood project [16] explores
using high-level data structures as the foundation for
storage. While compelling for more complicated al-
gorithms, abstractions like B-trees may not have high
impact on Haystack’s intentionally lean interface and
semantics. Similarly, Sinfonia’s [1] mini-transactions
and PNUTS’s [5] database functionality provide more
features and stronger guarantees than Haystack needs.
Ghemawat et al. [9] designed the Google File System
for a workload consisting mostly of append operations
and large sequential reads. Bigtable [4] provides a stor-
age system for structured data and offers database-like
features for many of Google’s projects. It is unclear
whether many of these features make sense in a system
optimized for photo storage.
6 Conclusion
This paper describes Haystack, an object storage sys-
tem designed for Facebook’s Photos application. We de-
signed Haystack to serve the long tail of requests seen
by sharing photos in a large social network. The key
insight is to avoid disk operations when accessing meta-
data. Haystack provides a fault-tolerant and simple solu-
tion to photo storage at dramatically less cost and higher
throughput than a traditional approach using NAS appli-
ances. Furthermore, Haystack is incrementally scalable,
a necessary quality as our users upload hundreds of mil-
lions of photos each week.
References
[1] M. K. Aguilera, A. Merchant, M. Shah, A. Veitch, and C. Kara-
manolis. Sinfonia: a new paradigm for building scalable dis-
tributed systems. In SOSP ’07: Proceedings of twenty-first ACM
SIGOPS symposium on Operating systems principles, pages
159-174, New York, NY, USA, 2007. ACM.
[2] Akamai. http://www.akamai.com/.
[3] M. G. Baker, J. H. Hartman, M. D. Kupfer, K. W. Shirriff, and
J. K. Ousterhout. Measurements of a distributed file system. In
Proc. 13th SOSP, pages 198-212, 1991.
[4] F. Chang, J. Dean, S. Ghemawat, W. C. Hsieh, D. A. Wallach,
M. Burrows, T. Chandra, A. Fikes, and R. E. Gruber. Bigtable:
[5]
[6]
[7]
[8]
[9]
[10]
[11]
[12]
[13]
[14]
[15]
[16]
[17]
[18]
[19]
A distributed storage system for structured data. ACM Trans.
Comput. Syst., 26(2):1-26, 2008.
B. F. Cooper, R. Ramakrishnan, U. Srivastava, A. Silberstein,
P. Bohannon, H.-A. Jacobsen, N. Puz, D. Weaver, and R. Yer-
neni. Pnuts: Yahoo!’s hosted data serving platform. Proc. VLDB
Endow., 1(2):1277-1288, 2008.
M. Dahlin, R. Wang, T. Anderson, and D. Patterson. Cooper-
ative Caching: Using Remote Client Memory to Improve File
System Performance. In Proceedings of the First Symposium on
Operating Systems Design and Implementation, pages 267-280,
Nov 1994.
M. Factor, K. Meth, D. Naor, O. Rodeh, and J. Satran. Object
storage: the future building block for storage systems. In LGDI
’05: Proceedings of the 2005 IEEE International Symposium on
Mass Storage Systems and Technology, pages 119-123, Wash-
ington, DC, USA, 2005. IEEE Computer Society.
G. R. Ganger and M. F. Kaashoek. Embedded inodes and ex-
plicit grouping: exploiting disk bandwidth for small files. In
ATEC ’97: Proceedings of the annual conference on USENIX
Annual Technical Conference, pages 1-1, Berkeley, CA, USA,
1997. USENIX Association.
S. Ghemawat, H. Gobioff, and S.-T. Leung. The google file
system. In Proc. 19th SOSP, pages 29-43. ACM Press, 2003.
G. A. Gibson, D. F. Nagle, K. Amiri, J. Butler, F. W. Chang,
H. Gobioff, C. Hardin, E. Riedel, D. Rochberg, and J. Zelenka.
A cost-effective, high-bandwidth storage architecture. SIGOPS
Oper. Syst. Rev., 32(5):92-103, 1998.
The hadoop project. http://hadoop.apache.org/.
S. He and D. Feng. Design of an object-based storage device
based on i/o processor. SIGOPS Oper. Syst. Rev., 42(6):30-35,
2008.
J. Hendricks, R. R. Sambasivan, S. Sinnamohideen, and G. R.
Ganger. Improving small file performance in object-based
storage. Technical Report 06-104, Parallel Data Laboratory,
Carnegie Mellon University, 2006.
E. K. Lee and C. A. Thekkath. Petal: distributed virtual disks. In
ASPLOS-VII: Proceedings of the seventh international confer-
ence on Architectural support for programming languages and
operating systems, pages 84-92, New York, NY, USA, 1996.
ACM.
A. W. Leung, M. Shao, T. Bisson, S. Pasupathy, and E. L. Miller.
Spyglass: fast, scalable metadata search for large-scale storage
systems. In FAST ’09: Proccedings of the 7th conference on File
and storage technologies, pages 153-166, Berkeley, CA, USA,
2009. USENIX Association.
J. MacCormick, N. Murphy, M. Najork, C. A. Thekkath, and
L. Zhou. Boxwood: abstractions as the foundation for storage
infrastructure. In OSDI’04: Proceedings of the 6th conference
on Symposium on Opearting Systems Design & Implementation,
pages 8-8, Berkeley, CA, USA, 2004. USENIX Association.
U. Manber and S. Wu. Glimpse: a tool to search through entire
file systems. In WTEC’94: Proceedings of the USENIX Winter
1994 Technical Conference on USENIX Winter 1994 Technical
Conference, pages 44, Berkeley, CA, USA, 1994. USENIX As-
sociation.
memcache. http://memcached.org/.
S. J. Mullender and A. S. Tanenbaum. Immediate files. Softw.
Pract. Exper., 14(4):365-368, 1984.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) 59
60
[20]
[21]
[22]
[23]
[24]
[25]
[26]
[27]
[28]
S. V. Patil, G. A. Gibson, S. Lang, and M. Polte. Giga+: scalable
directories for shared file systems. In PDSW ’07: Proceedings of
the 2nd international workshop on Petascale data storage, pages
26-29, New York, NY, USA, 2007. ACM.
Posix. http://standards.ieee.org/regauth/posix/.
Randomio. http://members.optusnet.com.au/clausen/ideas/randomio/index.html.
M. Rosenblum and J. K. Ousterhout. The design and implemen-
tation of a log-structured file system. ACM Trans. Comput. Syst.,
10(1):26-52, 1992.
A. Sweeney, D. Doucette, W. Hu, C. Anderson, M. Nishimoto,
and G. Peck. Scalability in the xfs file system. In ATEC ’96:
Proceedings of the 1996 annual conference on USENIX Annual
Technical Conference, pages 1-1, Berkeley, CA, USA, 1996.
USENIX Association.
F. Wang, S. A. Brandt, E. L. Miller, and D. D. E. Long. Obfs: A
file system for object-based storage devices. In In Proceedings
of the 21st IEEE / 12TH NASA Goddard Conference on Mass
Storage Systems and Technologies, pages 283-300, 2004.
S. A. Weil, S. A. Brandt, E. L. Miller, D. D. E. Long, and
C. Maltzahn. Ceph: a scalable, high-performance distributed
file system. In OSDI ’06: Proceedings of the 7th symposium on
Operating systems design and implementation, pages 307-320,
Berkeley, CA, USA, 2006. USENIX Association.
S. A. Weil, K. T. Pollack, S. A. Brandt, and E. L. Miller. Dy-
namic metadata management for petabyte-scale file systems. In
SC ’04: Proceedings of the 2004 ACM/IEEE conference on Su-
percomputing, page 4, Washington, DC, USA, 2004. IEEE Com-
puter Society.
Z. Zhang and K. Ghose. hfs: a hybrid file system prototype for
improving small file and metadata performance. SIGOPS Oper.
Syst. Rev., 41(3):175—187, 2007.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
USENIX Association
USENIX Association
Availability in Globally Distributed Storage Systems
Daniel Ford, Francois Labelle, Florentina I. Popovici, Murray Stokely, Van-Anh Truong;
*
Luiz Barroso, Carrie Grimes, and Sean Quinlan
{ford, flab, florentina,mstokely}@google.com, vatruong@ieor.columbia.edu
{luiz,cgrimes, sean}@google.com
Google, Inc.
Abstract
Highly available cloud storage is often implemented with
complex, multi-tiered distributed systems built on top
of clusters of commodity servers and disk drives. So-
phisticated management, load balancing and recovery
techniques are needed to achieve high performance and
availability amidst an abundance of failure sources that
include software, hardware, network connectivity, and
power issues. While there is a relative wealth of fail-
ure studies of individual components of storage systems,
such as disk drives, relatively little has been reported so
far on the overall availability behavior of large cloud-
based storage services.
We characterize the availability properties of cloud
storage systems based on an extensive one year study of
Google’s main storage infrastructure and present statis-
tical models that enable further insight into the impact
of multiple design choices, such as data placement and
replication strategies. With these models we compare
data availability under a variety of system parameters
given the real patterns of failures observed in our fleet.
1 Introduction
Cloud storage is often implemented by complex multi-
tiered distributed systems on clusters of thousands of
commodity servers. For example, in Google we run
Bigtable [9], on GFS [16], on local Linux file systems
that ultimately write to local hard drives. Failures in any
of these layers can cause data unavailability.
Correctly designing and optimizing these multi-
layered systems for user goals such as data availability
relies on accurate models of system behavior and perfor-
mance. In the case of distributed storage systems, this
includes quantifying the impact of failures and prioritiz-
ing hardware and software subsystem improvements in
*Now at Dept. of Industrial Engineering and Operations Research
Columbia University
the datacenter environment.
We present models we derived from studying a year of
live operation at Google and describe how our analysis
influenced the design of our next generation distributed
storage system [22].
Our work is presented in two parts. First, we measured
and analyzed the component availability, e.g. machines,
racks, multi-racks, in tens of Google storage clusters. In
this part we:
e Compare mean time to failure for system compo-
nents at different granularities, including disks, ma-
chines and racks of machines. (Section 3)
e Classify the failure causes for storage nodes, their
characteristics and contribution to overall unavail-
ability. (Section 3)
e Apply a clustering heuristic for grouping failures
which occurs almost simultaneously and show that
a large fraction of failures happen in bursts. (Sec-
tion 4)
e Quantify how likely a failure burst is associated
with a given failure domain. We find that most large
bursts of failures are associated with rack- or multi-
rack level events. (Section 4)
Based on these results, we determined that the criti-
cal element in models of availability is their ability to
account for the frequency and magnitude of correlated
failures.
Next, we consider data availability by analyzing un-
availability at the distributed file system level, where one
file system instance is referred to as a cell. We apply two
models of multi-scale correlated failures for a variety of
replication schemes and system parameters. In this part
we:
e Demonstrate the importance of modeling correlated
failures when predicting availability, and show their
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) 61
62
impact under a variety of replication schemes and
placement policies. (Sections 5 and 6)
e Formulate a Markov model for data availability, that
can scale to arbitrary cell sizes, and captures the in-
teraction of failures with replication policies and re-
covery times. (Section 7)
e Introduce multi-cell replication schemes and com-
pare the availability and bandwidth trade-offs
against single-cell schemes. (Sections 7 and 8)
e Show the impact of hardware failure on our cells is
significantly smaller than the impact of effectively
tuning recovery and replication parameters. (Sec-
tion 8)
Our results show the importance of considering
cluster-wide failure events in the choice of replication
and recovery policies.
2 Background
We study end to end data availability in a cloud com-
puting storage environment. These environments often
use loosely coupled distributed storage systems such as
GFS [1, 16] due to the parallel I/O and cost advantages
they provide over traditional SAN and NAS solutions. A
few relevant characteristics of such systems are:
e Storage server programs running on physical ma-
chines in a datacenter, managing local disk storage
on behalf of the distributed storage cluster. We refer
to the storage server programs as storage nodes or
nodes.
e A pool of storage service masters managing data
placement, load balancing and recovery, and moni-
toring of storage nodes.
e A replication or erasure code mechanism for user
data to provide resilience to individual component
failures.
A large collection of nodes along with their higher
level coordination processes [17] are called a cell or
storage cell. These systems usually operate in a shared
pool of machines running a wide variety of applications.
A typical cell may comprise many thousands of nodes
housed together in a single building or set of colocated
buildings.
2.1. Availability
A storage node becomes unavailable when it fails to re-
spond positively to periodic health checking pings sent
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
100 T T T
BO ated pai fie re ale eect 4
Be BNR ae foes has es 4
2
=|
g AQ Reef see then acs ceed teed eee 4
wu
BO Pie eee ees ne ied rein ee teins 4
0 i i
1s 10s 1min 15min 1h 6h 1d 7d 1mon
Unavailability event duration
Figure 1: Cumulative distribution function of the duration of
node unavailability periods.
by our monitoring system. The node remains unavail-
able until it regains responsiveness or the storage system
reconstructs the data from other surviving nodes.
Nodes can become unavailable for a large number of
reasons. For example, a storage node or networking
switch can be overloaded; a node binary or operating
system may crash or restart; a machine may experience
a hardware error; automated repair processes may tem-
porarily remove disks or machines; or the whole clus-
ter could be brought down for maintenance. The vast
majority of such unavailability events are transient and
do not result in permanent data loss. Figure | plots the
CDF of node unavailability duration, showing that less
than 10% of events last longer than 15 minutes. This
data is gathered from tens of Google storage cells, each
with 1000 to 7000 nodes, over a one year period. The
cells are located in different datacenters and geographi-
cal regions, and have been used continuously by different
projects within Google. We use this dataset throughout
the paper, unless otherwise specified.
Experience shows that while short unavailability
events are most frequent, they tend to have a minor im-
pact on cluster-level availability and data loss. This is
because our distributed storage systems typically add
enough redundancy to allow data to be served from other
sources when a particular node is unavailable. Longer
unavailability events, on the other hand, make it more
likely that faults will overlap in such a way that data
could become unavailable at the cluster level for long
periods of time. Therefore, while we track unavailabil-
ity metrics at multiple time scales in our system, in this
paper we focus only on events that are 15 minutes or
longer. This interval is long enough to exclude the ma-
jority of benign transient events while not too long to ex-
clude significant cluster-wide phenomena. As in [11], we
observe that initiating recovery after transient failures is
inefficient and reduces resources available for other op-
erations. For these reasons, GFS typically waits 15 min-
utes before commencing recovery of data on unavailable
nodes.
USENIX Association
USENIX Association
We primarily use two metrics throughout this paper.
The average availability of all N nodes in a cell is defined
as:
>in, en Uptime(N;)
A =
= > n,en (uptime(N;) + downtime (N;))
()
We use uptime(/V;) and downtime(JV;) to refer to the
lengths of time a node JN; is available or unavailable, re-
spectively. The sum of availability periods over all nodes
is called node uptime. We define uptime similarly for
other component types. We define unavailability as the
complement of availability.
Mean time to failure, or MTTF, is commonly quoted
in the literature related to the measurements of availabil-
ity. We use MTTF for components that suffer transient
or permanent failures, to avoid frequent switches in ter-
minology.
uptime
MTTF = (2)
Availability measurements for nodes and individual
components in our system are presented in Section 3.
number failures
2.2 Data replication
Distributed storage systems increase resilience to fail-
ures by using replication [2] or erasure encoding across
nodes [28]. In both cases, data is divided into a set of
stripes, each of which comprises a set of fixed size data
and code blocks called chunks. Data in a stripe can be re-
constructed from some subsets of the chunks. For repli-
cation, R = n refers to n identical chunks in a stripe,
so the data may be recovered from any one chunk. For
Reed-Solomon erasure encoding, RS(n,m) denotes n
distinct data blocks and m error correcting blocks in each
stripe. In this case a stripe may be reconstructed from any
n chunks.
We call a chunk available if the node it is stored on
is available. We call a stripe available if enough of its
chunks are available to reconstruct the missing chunks,
if any.
Data availability is a complex function of the individ-
ual node availability, the encoding scheme used, the dis-
tribution of correlated node failures, chunk placement,
and recovery times that we will explore in the second part
of this paper. We do not explore related mechanisms for
dealing with failures, such as additional application level
redundancy and recovery, and manual component repair.
3 Characterizing Node Availability
Anything that renders a storage node unresponsive is
a potential cause of unavailability, including hardware
component failures, software bugs, crashes, system re-
boots, power loss events, and loss of network connec-
tivity. We include in our analysis the impact of software
upgrades, reconfiguration, and other maintenance. These
planned outages are necessary in a fast evolving datacen-
ter environment, but have often been overlooked in other
availability studies. In this section we present data for
storage node unavailability and provide some insight into
the main causes for unavailability.
3.1 Numbers from the fleet
Failure patterns vary dramatically across different hard-
ware platforms, datacenter operating environments, and
workloads. We start by presenting numbers for disks.
Disks have been the focus of several other studies,
since they are the system component that permanently
stores the data, and thus a disk failure potentially results
in permanent data loss. The numbers we observe for disk
and storage subsystem failures, presented in Table 2, are
comparable with what other researchers have measured.
One study [29] reports ARR (annual replacement rate)
for disks between 2% and 4%. Another study [19] fo-
cused on storage subsystems, thus including errors from
shelves, enclosures, physical interconnects, protocol fail-
ures, and performance failures. They found AFR (annual
failure rate) generally between 2% and 4%, but for some
storage systems values ranging between 3.9% and 8.3%.
For the purposes of this paper, we are interested in
disk errors as perceived by the application layer. This
includes latent sector errors and corrupt sectors on disks,
as well as errors caused by firmware, device drivers, con-
trollers, cables, enclosures, silent network and memory
corruption, and software bugs. We deal with these er-
rors with background scrubbing processes on each node,
as in [5, 31], and by verifying data integrity during client
reads [4]. Background scrubbing in GFS finds between
1 in 10° to 10’ of older data blocks do not match the
checksums recorded when the data was originally writ-
ten. However, these cell-wide rates are typically concen-
trated on a small number of disks.
We are also concerned with node failures in addition
to individual disk failures. Figure 2 shows the distribu-
tion of three mutually exclusive causes of node unavail-
ability in one of our storage cells. We focus on node
restarts (software restarts of the storage program running
on each machine), planned machine reboots (e.g. ker-
nel version upgrades), and unplanned machine reboots
(e.g. kernel crashes). For the purposes of this figure we
do not exclude events that last less than 15 minutes, but
we still end the unavailability period when the system
reconstructs all the data previously stored on that node.
Node restart events exhibit the greatest variability in du-
ration, ranging from less than one minute to well over an
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) 63
64
100
“Node restarts
if
i/ Planned reboots --------
Ray ie ee Unplanned reboots --—---- :
a al
e 60 a
2 i
7 : f a
> 40 ar : FOR Sar ee
Ww : : : {
20 [Ae eee ene
PoE
0 a 4 Lt
1s 10s imin 15minth 6h 1d
Unavailability event duration
7d 1mon
Figure 2: Cumulative distribution function of node unavailabil-
ity durations by cause.
40 5
s
TS
©
Qa
6 30-7
8 m@ Unknown
= Node restarts
S H Planned reboots
= Unplanned reboots
= 20-4 P
Qa
n
<
)
>
wu
10 5
0 |
Time (months)
Figure 3: Rate of events per 1000 nodes per day, for one exam-
ple cell.
hour, though they usually have the shortest duration. Un-
planned reboots have the longest average duration since
extra checks or corrective action is often required to re-
store machines to a safe state.
Figure 3 plots the unavailability events per 1000 nodes
per day for one example cell, over a period of three
months. The number of events per day, as well as the
number of events that can be attributed to a given cause
vary significantly over time as operational processes,
tools, and workloads evolve. Events we cannot classify
accurately are labeled unknown.
The effect of machine failures on availability is de-
pendent on the rate of failures, as well as on how long
the machines stay unavailable. Figure 4 shows the node
unavailability, along with the causes that generated the
unavailability, for the same cell used in Figure 3. The
availability is computed with a one week rolling window,
using definition (1). We observe that the majority of un-
availability is generated by planned reboots.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
0.035 +
0.030 +
0.025 +
mM Unknown
Node restarts
B Planned reboots
Unplanned reboots
0.020
Unavailability (%)
0.015
0.010
0.005
0.000
Time (months)
Figure 4: Storage node unavailability computed with a one
week rolling window, for one example cell.
Cause Unavailability (%)
average / min / max
Node restarts 0.0139 / 0.0004 / 0.1295
Planned machine reboots 0.0154 / 0.0050 / 0.0563
Unplanned machine reboots | 0.0025 / 0.0000 / 0.0122
Unknown 0.0142 / 0.0013 / 0.0454
Table 1: Unavailability attributed to different failure causes,
over the full set of cells.
Table 1 shows the unavailability from node restarts,
planned and unplanned machine reboots, each of which
is a significant cause. The numbers are exclusive, thus
the planned machine reboots do not include node restarts.
Table 2 shows the MTTF for a series of important
components: disk, nodes, and racks of nodes. The num-
bers we report for component failures are inclusive of
software errors and hardware failures. Though disks fail-
ures are permanent and most node failures are transitory,
the significantly greater frequency of node failures makes
them a much more important factor for system availabil-
ity (Section 8.4).
4 Correlated Failures
The co-occurring failure of a large number of nodes
can reduce the effectiveness of replication and encoding
schemes. Therefore it is critical to take into account the
Statistical behavior of correlated failures to understand
data availability. In this section we are more concerned
with measuring the frequency and severity of such fail-
ures rather than root causes.
USENIX Association
USENIX Association
Component Disk Node Rack
MTTF 10-50 years | 4.3 months | 10.2 years
Table 2: Component failures across several Google cells.
Time intervals when a
>2min node is unavailable
B.S J burst
8
| ——— J
Time (min)
0 5 10 15 20 25 30
Figure 5: Seven node failures clustered into two failure bursts
when the window size is 2 minutes. Note how only the unavail-
ability start times matter.
We define a failure burst and examine features of these
bursts in the field. We also develop a method for identi-
fying which bursts are likely due to a failure domain. By
failure domain, we mean a set of machines which we ex-
pect to simultaneously suffer from a common source of
failure, such as machines which share a network switch
or power cable. We demonstrate this method by validat-
ing physical racks as an important failure domain.
4.1 Defining failure bursts
We define a failure burst with respect to a window size
w as a maximal sequence of node failures, each one oc-
curring within a time window w of the next. Figure 5
illustrates the definition. We choose w = 120 s, for sev-
eral reasons. First, it is longer than the frequency with
which nodes are periodically polled in our system for
their status. A window length smaller than the polling
interval would not make sense as some pairs of events
which actually occur within the window length of each
other would not be correctly associated. Second, it is less
than a tenth of the average time it takes our system to re-
cover a chunk, thus, failures within this window can be
considered as nearly concurrent. Figure 6 shows the frac-
tion of individual failures that get clustered into bursts of
at least 10 nodes as the window size changes. Note that
the graph is relatively flat after 120 s, which is our third
reason for choosing this value.
Since failures are clustered into bursts based on their
times of occurrence alone, there is a risk that two bursts
with independent causes will be clustered into a single
burst by chance. The slow increase in Figure 6 past 120s
illustrates this phenomenon. The error incurred is small
as long as we keep the window size small. Given a win-
dow size of 120 s and the set of bursts obtained from it,
the probability that a random failure gets included in a
14 T T T T T
12
10
Node failures (%)
OoOnN Ff OD
0 100 200 300 400 500 600
Window size (s)
Figure 6: Effect of the window size on the fraction of individual
failures that get clustered into bursts of at least 10 nodes.
burst (as opposed to becoming its own singleton burst)
is 8.0%. When this inclusion happens, most of the time
the random failure is combined with a singleton burst to
form a burst of two nodes. The probability that a random
failure gets included in a burst of at least 10 nodes is only
0.068%. For large bursts, which contribute most unavail-
ability as we will see in Section 5.2, the fraction of nodes
affected is the significant quantity and changes insignifi-
cantly if a burst of size one or two nodes is accidentally
clustered with it.
Using this definition, we observe that 37% of failures
are part of a burst of at least 2 nodes. Given the result
above that only 8.0% of non-correlated failures may be
incorrectly clustered, we are confident that close to 37%
of failures are truly correlated.
4.2 Views of failure bursts
Figure 7 shows the accumulation of individual failures in
bursts. For clarity we show all bursts of size at least 10
seen over a 60 day period in an example cell. In the plot,
each burst is displayed with a separate shape. The n-th
node failure that joins a burst at time ¢,, is said to have
ordinal n — 1 and is plotted at point (¢,, — 1). Two
broad classes of failure bursts can be seen in the plot:
1. Those failure bursts that are characterized by a large
number of failures in quick succession show up as
steep lines with a large number of nodes in the burst.
Such failures can be seen, for example, following a
power outage in a datacenter.
2. Those failure bursts that are characterized by a
smaller number of nodes failing at a slower rate
at evenly spaced intervals. Such correlated failures
can be seen, for example, as part of rolling reboot
or upgrade activity at the datacenter management
layer.
Figure 8 displays the bursts sorted by the number of
nodes and racks that they affect. The size of each bubble
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) 65
66
504
$
40 4
B
B 30-4
&
s
2
ow
s 20 || ghee +7
6 a ee
Ae
2° :*
10 + : 3
o ox yee
—% _ gh toot
of gt
o4 &
T i T T T T T
0 100 200 300 400 500 600
Time from start of burst (s)
Figure 7: Development of failure bursts in one example cell.
indicates the frequency of each burst group. The group-
ing of points along the 45° line represent bursts where
as many racks are affected as nodes. The points furthest
away from this line represent the most rack-correlated
failure bursts. For larger bursts of at least 10 nodes, we
find only 3% have all their nodes on unique racks. We
introduce a metric to quantify this degree of domain cor-
relation in the next section.
4.3 Identifying domain-related failures
Domain-related issues, such those associated with phys-
ical racks, network switches and power domains, are fre-
quent causes of correlated failure. These problems can
sometimes be difficult to detect directly. We introduce
a metric to measure the likelihood that a failure burst is
domain-related, rather than random, based on the pat-
tern of failure observed. The metric can be used as an
effective tool for identifying causes of failures that are
connected to domain locality. It can also be used to eval-
uate the importance of domain diversity in cell design
and data placement. We focus on detecting rack-related
node failures in this section, but our methodology can be
applied generally to any domain and any type of failure.
Let a failure burst be encoded as an n-tuple
(ki, ke,...,kn), where ky < kg < ... < kp. Each
k, gives the number of nodes affected in the 7-th rack af-
fected, where racks are ordered so that these values are
increasing. This rack-based encoding captures all rele-
vant information about the rack locality of the burst. Let
the size of the burst be the number of nodes that are af-
fected, ie., )>;"_, kj. We define the rack-affinity score of
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
500 =
200 + ° = .
oD 4 Bayt oat
3 100 € 98 es .
8 ° o
£ ® : 3°
® 007. i ° :
oO & o 48
2 S : ue
2 20- fo ee
—
2 ° 1 occurrence
2 104
S O 10 occurrences
Zz 54
C) 100 occurrences
2 pa
1000 occurrences
1 =
T T T T T T T T T
1 2 5 10 20 50 100 200 500
Number of racks affected
Figure 8: Frequency of failure bursts sorted by racks and nodes
affected.
a burst to be
» ki (ky — 1)
: 2
Note that this is the number of ways of choosing two
nodes from the burst within the same rack. The score
allows us to compare the rack concentration of bursts of
the same size. For example the burst (1,4) has score 6.
The burst (1,1, 1,2) has score 1 which is lower. There-
fore, the first burst is more concentrated by rack. Possi-
ble alternatives for the score include the sum of squares
Soy, k? or the negative entropy 5>;"_, k; log(k;). The
sum of squares formula is equivalent to our chosen score
because for a fixed burst size, the two formulas are re-
lated by an affine transform. We believe the entropy-
inspired formula to be inferior because its log factor
tends to downplay the effect of a very large k;. Its real-
valued score is also a problem for the dynamic program
we use later in computation.
We define the rack affinity of a burst in a particular cell
to be the probability that a burst of the same size affecting
randomly chosen nodes in that cell will have a smaller
burst score, plus half the probability that the two scores
are equal, to eliminate bias. Rack affinity is therefore a
number between 0 and 1 and can be interpreted as a ver-
tical position on the cumulative distribution of the scores
of random bursts of the same size. It can be shown that
for a random burst, the expected value of its rack affin-
ity is exactly 0.5. So we define a rack-correlated burst
to be one with a metric close to 1, a rack-uncorrelated
burst to be one with a metric close to 0.5, and a rack-
anti-correlated burst to be one with a metric close to 0
(we have not observed such a burst). It is possible to ap-
USENIX Association
USENIX Association
proximate the metric using simulation of random bursts.
We choose to compute the metric exactly using dynamic
programming because the extra precision it provides al-
lows us to distinguish metric values very close to 1.
We find that, in general, larger failure bursts have
higher rack affinity. All our failure bursts of more than
20 nodes have rack affinity greater than 0.7, and those
of more than 40 nodes have affinity at least 0.9. It is
worth noting that some bursts with high rack affinity do
not affect an entire rack and are not caused by common
network or power issues. This could be the case for a
bad batch of components or new storage node binary or
kernel, whose installation is only slightly correlated with
these domains.
5 Coping with Failure
We now begin the second part of the paper where we
transition from node failures to analyzing replicated data
availability. Two methods for coping with the large num-
ber of failures described in the first part of this paper
include data replication and recovery, and chunk place-
ment.
5.1 Data replication and recovery
Replication or erasure encoding schemes provide re-
silience to individual node failures. When a node fail-
ure causes the unavailability of a chunk within a stripe,
we initiate a recovery operation for that chunk from the
other available chunks remaining in the stripe.
Distributed filesystems will necessarily employ
queues for recovery operations following node failure.
These queues prioritize reconstruction of stripes which
have lost the most chunks. The rate at which missing
chunks may be recovered is limited by the bandwidth of
individual disks, nodes, and racks. Furthermore, there
is an explicit design tradeoff in the use of bandwidth
for recovery operations versus serving client read/write
requests.
3 Unavailable Chunks in Stripe
2 Unavailable Chunks in Stripe
1 Unavailable Chunk in Stripe
‘| ,
0 2500 5500 8500 12000 16000 20000
oOo
100000 200000
Chunk Recoveries
Seconds from Server/Disk Failure to Chunk Recovery Initiation
Figure 9: Example chunk recovery after failure bursts.
This limit is particularly apparent during correlated
failures when a large number of chunks go missing at the
same time. Figure 9 shows the recovery delay after a fail-
ure burst of 20 storage nodes affecting millions of stripes.
Operators may adjust the rate-limiting seen in the figure.
1e+20 ——-— «wm —_ _|
if + RS(20,10) + |
10418 be to RS(9,4) x 4
kt RS(5,3)_® 4
1e+16 - Mertens ae o +
no L Oo = a
Bl teetd: Reet eters R=2 ©
= L * R=1 e
Le DOT erecta creer cecececececececeeeeeeeceeeeeeseeeeeereeeceeeeacaraeesaneneneennntey 4
L 2 a
E 1e+10 bm enn seer 4
® i + +
D 19108 aces a. a
we “Aiea iil a dieretacti ° Bed soe
2 | 2 ° °o
10000. bp -—-- oe 4
k e e
small bursts medium bursts large bursts
Figure 10: Stripe MTTF due to different burst sizes. Burst sizes
are defined as a fraction of all nodes: small (0-0.001), medium
(0.001-0.01), large (0.01-0.1). For each size, the left column
represents uniform random placement, and the right column
represents rack-aware placement.
The models presented in the following sections allow us
to measure the sensitivity of data availability to this rate-
limit and other parameters, described in Section 8.
5.2 Chunk placement and stripe unavailability
To mitigate the effect of large failure bursts in a single
failure domain we consider known failure domains when
placing chunks within a stripe on storage nodes. For ex-
ample, racks constitute a significant failure domain to
avoid. A rack-aware policy is one that ensures that no
two chunks in a stripe are placed on nodes in the same
rack.
Given a failure burst, we can compute the expected
fraction of stripes made unavailable by the burst. More
generally, we compute the probability that exactly k
chunks are affected in a stripe of size n, which is es-
sential to the Markov model of Section 7. Assuming that
stripes are uniformly distributed across nodes of the cell,
this probability is a ratio where the numerator is the num-
ber of ways to place a stripe of size n in the cell such
that exactly k of its chunks are affected by the burst, and
the denominator is the total number of ways to place a
stripe of size n in the cell. These numbers can be com-
puted combinatorially. The same ratio can be used when
chunks are constrained by a placement policy, in which
case the numerator and denominator are computed using
dynamic programming.
Figure 10 shows the stripe MTTF for three classes of
burst size. For each class of bursts we calculate the av-
erage fraction of stripes affected per burst and the rate
of bursts, to get the combined MTTF due to that class.
We see that for all encodings except R = 1, large fail-
ure bursts are the biggest contributor to unavailability
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) 67
68
despite the fact that they are much rarer. We also see
that for small and medium bursts sizes, and large encod-
ings, using a rack-aware placement policy increases the
stripe MTTF by a factor of 3 typically. This is a signifi-
cant gain considering that in uniform random placement,
most stripes end up with their chunks on different racks
due to chance.
6 Cell Simulation
This section introduces a trace-based simulation method
for calculating availability in a cell. The method replays
observed or synthetic sequences of node failures and cal-
culates the resulting impact on stripe availability. It of-
fers detailed view of availability in short time frames.
For each node, the recorded events of interest are
down, up and recovery complete events. When all nodes
are up, they are each assumed to be responsible for an
equal number of chunks. When a node goes down it
is still responsible for the same number of chunks until
15 minutes later when the chunk recovery process starts.
For simplicity and conservativeness, we assume that all
these chunks remain unavailable until the recovery com-
plete event. A more accurate model could model recov-
ery too, such as by reducing the number of unavailable
chunks linearly until the recovery complete event, or by
explicitly modelling recovery queues.
We are interested in the expected number of stripes
that are unavailable for at least 15 minutes, as a function
of time. Instead of simulating a large number of stripes,
it is more efficient to simulate all possible stripes, and use
combinatorial calculations to obtain the expected number
of unavailable stripes given a set of down nodes, as was
done in Section 5.2.
As a validation, we can run the simulation using the
stripe encodings that were in use at the time to see if the
predicted number of unavailable stripes matches the ac-
tual number of unavailable stripes as measured by our
storage system. Figure 11 shows the result of such a
simulation. The prediction is a linear combination of the
predictions for individual encodings present, in this case
mostly RS(5,3) and R = 3.
Analysis of hypothetical scenarios may also be made
with the cell simulator, such as the effect of encoding
choice and of chunk recovery rate. Although we may
not change the frequency and severity of bursts in an ob-
served sequence, bootstrap methods [13] may be used
to generate synthetic failure traces with different burst
characteristics. This is useful for exploring sensitivity to
these events and the impact of improvements in datacen-
ter reliability.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
0.001
Measured ——— ]
Predicted
0.0001
1e-05
1e-06
1e-07
1e-08
1e-09
Fraction of unavailable stripes
te-10
18:00
1e-11
0:00 6:00 12:00
Time of day
24:00
Figure 11: Unavailability prediction over time for a particular
cell for a day with large failure bursts.
7 Markov Model of Stripe Availability
In this section, we formulate a Markov model of data
availability. The model captures the interaction of dif-
ferent failure types and production parameters with more
flexibility than is possible with the trace-based simula-
tion described in the previous section. Although the
model makes assumptions beyond those in the trace-
based simulation method, it has certain advantages. First,
it allows us to model and understand the impact of
changes in hardware and software on end-user data avail-
ability. There are typically too many permutations of sys-
tem changes and encodings to test each in a live cell. The
Markov model allows us to reason directly about the con-
tribution to data availability of each level of the storage
stack and several system parameters, so that we can eval-
uate tradeoffs. Second, the systems we study may have
unavailability rates that are so low they are difficult to
measure directly. The Markov model handles rare events
and arbitrarily low stripe unavailability rates efficiently.
The model focuses on the availability of a representa-
tive stripe. Let s be the total number of chunks in the
stripe, and r be the minimum number of chunks needed
to recover that stripe. As described in Section 2.2, r = 1
for replicated data and r = n for RS(n,m) encoded
data. The state of a stripe is represented by the number of
available chunks. Thus, the states are s,s—1,...,r,r—1
with the state r — 1 representing all of the unavailable
states where the stripe has less than the required r chunks
available. Figure 12 shows a Markov chain correspond-
ing to an R = 2 stripe.
The Markov chain transitions are specified by the rates
at which a stripe moves from one state to another, due to
chunk failures and recoveries. Chunk failures reduce the
number of available chunks, and several chunks may fail
‘simultaneously’ in a failure burst event. Balancing this,
recoveries increase the number of available chunks if any
USENIX Association
USENIX Association
Chunk recovery Stripe unavailable
Cr)
Chunk failure
Figure 12: The Markov chain for a stripe encoded using R = 2.
are unavailable.
A key assumption of the Markov model is that events
occur independently and with constant rates over time.
This independence assumption, although strong, is not
the same as the assumption that individual chunks fail
independently of each other. Rather, it implies that fail-
ure events are independent of each other, but each event
may involve multiple chunks. This allows a richer and
more flexible view of the system. It also implies that re-
covery rates for a stripe depend only on its own current
state.
In practice, failure events are not always independent.
Most notably, it has been pointed out in [29] that the time
between disk failures is not exponentially distributed and
exhibits autocorrelation and long-range dependence. The
Weibull distribution provides a much better fit for disk
MTTF.
However, the exponential distribution is a reason-
able approximation for the following reasons. First, the
Weibull distribution is a generalization of the exponen-
tial distribution that allows the rate parameter to increase
over time to reflect the aging of disks. In a large pop-
ulation of disks, the mixture of disks of different ages
tends to be stable, and so the average failure rate in a
cell tends to be constant. When the failure rate is stable,
the Weibull distribution provides the same quality of fit
as the exponential. Second, disk failures make up only
a small subset of failures that we examined, and model
results indicate that overall availability is not particularly
sensitive to them. Finally, other authors ([24]) have con-
cluded that correlation and non-homogeneity of the re-
covery rate and the mean time to a failure event have
a much smaller impact on system-wide availability than
the size of the event.
7.1 Construction of the Markov chain
We compute the transition rate due to failures using ob-
served failure events. Let \ denote the rate of failure
events affecting chunks, including node and disk failures.
For any observed failure event we compute the probabil-
ity that it affects & chunks out of the 7 available chunks in
a Stripe. As in Section 6, for failure bursts this computa-
tion takes into account the stripe placement strategy. The
rate and severity of bursts, node, disk, and other failures
may be adjusted here to suit the system parameters under
exploration.
Averaging these probabilities over all failures events
gives the probability, p;,;, that a random failure event will
affect i—7 out of 2 available chunks in a stripe. This gives
a rate of transition from state 7 to state 7 < 7, of Ai; =
Api,j fors > i > j > rand Ayy-1 = A a Dig
for the rate of reaching the unavailable state. Note that
transitions from a state to itself are ignored.
For chunk recoveries, we assume a fixed rate of p for
recovering a single chunk, i.e. moving from a state 7 to
7+ 1, where r <2 < s. In particular, this means we as-
sume that the recovery rate does not depend on the total
number of unavailable chunks in the cell. This is justi-
fied by setting p to a lower bound for the rate of recovery,
based on observed recovery rates across our storage cells
or proposed system performance parameters. While par-
allel recovery of multiple chunks from a stripe is possi-
ble, p:,i41 = (s — 1)p, we model serial recovery to gain
more conservative estimates of stripe availability.
As with [12], the distributed systems we study use pri-
oritized recovery for stripes with more than one chunk
unavailable. Our Markov model allows state-dependent
recovery that captures this prioritization, but for ease of
exposition we do not use this added degree of freedom.
Finally, transition rates between pairs of states not
mentioned are zero.
With the Markov chain thus completely specified,
computing the MTTF of a stripe, as the mean time to
reach the ‘unavailable state’ r — 1 starting from state s,
follows by standard methods [27].
7.2 Extension to multi-cell replication
The models introduced so far can be extended to compute
the availability of multi-cell replication schemes. An ex-
ample of such a scheme is R = 3 x 2, where six replicas
of the data are distributed as R = 3 replication in each of
two linked cells. If data becomes unavailable at one cell
then it is automatically recovered from another linked
cell. These cells may be placed in separate datacenters,
even on separate continents. Reed-Solomon codes may
also be used, giving schemes such as RS(6,3) x 3 for
three cells each with a RS(6,3) encoding of the data.
We do not consider here the case when individual chunks
may be combined from multiple cells to recover data, or
other more complicated multi-cell encodings.
We compute the availability of stripes that span cells
by building on the Markov model just presented. Intu-
itively, we treat each cell as a ‘chunk’ in the multi-cell
‘stripe’, and compute its availability using the Markov
model. We assume that failures at different data centers
are independent, that is, that they lack a single point of
failure such as a shared power plant or network link. Ad-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) 69
70
ditionally, when computing the cell availability, we ac-
count for any cell-level or datacenter-level failures that
would affect availability.
We build the corresponding transition matrix that
models the resulting multi-cell availability as follows.
We start from the transition matrices M; for each cell,
as explained in the previous section. We then build the
transition matrix for the combined scheme as the tensor
product of these, ®; M,, plus terms for whole cell fail-
ures, and for cross-cell recoveries if the data becomes
unavailable in some cells but is still available in at least
one cell. However, it is a fair approximation to simply
treat each cell as a highly-reliable chunk in a multi-cell
stripe, as described above.
Besides symmetrical cases, such as R = 3 x 2 repli-
cation, we can also model inhomogeneous replication
schemes, such as one cell with R = 3 and one with
R = 2. The state space of the Markov model is the
product of the state space for each cell involved, but may
be approximated again by simply counting how many of
each type of cell is available.
A point of interest here is the recovery bandwidth be-
tween cells, quantified in Section 8.5. Bandwidth be-
tween distant cells has significant cost which should
be considered when choosing a multi-cell replication
scheme.
8 Markov Model Findings
In this section, we apply the Markov models described
above to understand how changes in the parameters of
the system will affect end-system availability.
8.1 Markov model validation
We validate the Markov model by comparing MTTF pre-
dicted by the model with actual MTTF values observed
in production cells. We are interested in whether the
Markov model provides an adequate tool for reasoning
about stripe availability. Our main goal in using the
model is providing a relative comparison of competing
storage solutions, rather than a highly accurate predic-
tion of any particular solution.
We underline two observations that surface from val-
idation. First, the model is able to capture well the ef-
fect of failure bursts, which we consider as having the
most impact on the availability numbers. For the cells we
observed, the model predicted MTTF with the same or-
der of magnitude as the measured MTTF. In one particu-
lar cell, besides more regular unavailability events, there
was a large failure burst where tens of nodes became un-
available. This resulted in an MTTF of 1.76E+6 days,
while the model predicted 5E+6 days. Though the rela-
tive error exceeds 100%, we are satisfied with the model
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
accuracy, since it still gives us a powerful enough tool to
make decisions, as can be seen in the following sections.
Second, the model can distinguish between failure
bursts that span racks, and thus pose a threat to availabil-
ity, and those that do not. If one rack goes down, then
without other events in the cell, the availability of stripes
with R=3 replication will not be affected, since the stor-
age system ensures that chunks in each stripe are placed
on different racks. For one example cell, we noticed tens
of medium sized failure bursts that affected one or two
racks. We expected the availability of the cell to stay
high, and indeed we measured MTTF = 29.52E+8 days.
The model predicted 5.77E+8 days. Again, the relative
error is significant, but for our purposes the model pro-
vides sufficiently accurate predictions.
Validating the model for all possible replication and
Reed-Solomon encodings is infeasible, since our produc-
tion cells are not set up to cover the complete space of
options. However, because of our large number of pro-
duction cells we are able to validate the model over a
range of encodings and operating conditions.
8.2 Importance of recovery rate
To develop some intuition about the sensitivity of stripe
availability to recovery rate, consider the situation where
there are no failure bursts. Chunks fail independently
with rate \ and recover with rate p. As in the previous
section, consider a stripe with s chunks total which can
survive losing at most s—r chunks, such as RS(r, 5 — r).
Thus the transition rate from state 2 > r to state i — 1 is
iA, and from state i toi+1lis pforr >i<s.
We compute the MTTF, given by the time taken to
reach state r — | starting in state s. Using standard meth-
ods related to Gambler’s Ruin, [8, 14, 15, 26], this comes
to:
where (a) (4) denotes (a)(a — 1)(a — 2)---(a—b+1).
Assuming recoveries take much less time than node
MTTF (i.e. p >> A), gives a stripe MTTF of:
eo 1 | O (—)
Aree (Ss) a
By similar computations, the recovery bandwidth con-
sumed is approximately As per r data chunks.
Thus, with no correlated failures reducing recovery
times by a factor of jz will increase stripe MTTF by a
factor of j:? for R = 3 and by pi* for RS(9, 4).
Reducing recovery times is effective when correlated
failures are few. For R.S'(6, 3) with no correlated failures,
a 10% reduction in recovery time results in a 19% reduc-
tion in unavailability. However, when correlated failures
USENIX Association
USENIX Association
Policy MTTF(days) with | MTTF(days) w/o
(% overhead) correlated failures | correlated failures
R = 2 (100) L47E +5 4.99E +05
R = 3 (200) 6.82E +6 1.35E + 09
R=4 (300) 1.404 +8 2.75E + 12
R=5 (400) 2.41E4+9 8.98EF + 15
RS(4,2) (50) | 1.80E +6 1.35E +09
RS(6,3) (50) | 1.032 +7 4.95E +12
RS(9, 4) (44) 2.39F + 6 9.01F + 15
RS(8,4) (50) | 5.11B +7 1.80E + 16
Table 3: Stripe MTTF in days, corresponding to various data
redundancy policies and space overhead.
Policy MTTF Bandwidth
(recovery time) (days) (per PB)
R=2x 2(1day) 1.08F + 10 | 6.8MB/day
R=2x 2(1hr) 2.58E +11 | 6.8MB/day
RS(6,3) x 21day) | 5.32h +13 | 97KB/day
RS(6,3) x 21hr) | 1.226+15 | 97KB/day
Table 4: Stripe MTTF and inter-cell bandwidth, for various
multi-cell schemes and inter-cell recovery times.
are taken into account, even a 90% reduction in recovery
time results in only a 6% reduction in unavailability.
8.3 Impact of correlation on effectiveness of data-
replication schemes
Table 3 presents stripe availability for several data-
replication schemes, measured in MTTF. We contrast
this with stripe MTTF when node failures occur at the
same total rate but are assumed independent.
Note that failing to account for correlation of node fail-
ures typically results in overestimating availability by at
least two orders of magnitude, and eight in the case of
RS(8,4). Correlation also reduces the benefit of increas-
ing data redundancy. The gain in availability achieved
by increasing the replication number, for example, grows
much more slowly when we have correlated failures.
Reed Solomon encodings achieve similar resilience to
failures compared to replication, though with less stor-
age overhead.
8.4 Sensitivity of availability to component failure
rates
One common method for improving availability is reduc-
ing component failure rates. By inserting altered failure
rates of hardware into the model we can estimate the im-
pact of potential improvements without actually building
or deploying new hardware.
We find that improvements below the node (server)
layer of the storage stack do not significantly improve
data availability. Assuming R = 3 is used, a 10% re-
duction in the latent disk error rate has a negligible effect
on stripe availability. Similarly, a 10% reduction in the
disk failure rate increases stripe availability by less than
1.5%. On the other hand, cutting node failure rates by
10% can increase data availability by 18%. This holds
generally for other encodings.
8.5 Single vs multi-cell replication schemes
Table 4 compares stripe MTTF under several multi-cell
replication schemes and inter-cell recovery times, taking
into consideration the effect of correlated failures within
cells.
Replicating data across multiple cells (data centers)
greatly improves availability because it protects against
correlated failures. For example, R = 2 x 2 with | day
recovery time between cells has two orders of magnitude
longer MTTF than R = 4, shown in Table 3.
This introduces a tradeoff between higher replication
in a single cell and the cost of inter-cell bandwidth. The
extra availability for R = 2 x 2 with | day recoveries ver-
sus R = 4 comes at an average cost of 6.8 MB/(user PB)
copied between cells each day. This is the inverse MTTF
for R = 2.
It should be noted that most cross-cell recoveries will
occur in the event of large failure bursts. This must be
considered when calculating expected recovery times be-
tween cells and the cost of on-demand access to poten-
tially large amounts of bandwidth.
Considering the relative cost of storage versus recov-
ery bandwidth allows us to choose the most cost effective
scheme given particular availability goals.
9 Related Work
Several previous studies [3, 19, 25, 29, 30] focus on the
failure characteristics of independent hardware compo-
nents, such as hard drives, storage subsystems, or mem-
ory. As we have seen, these must be included when con-
sidering availability but by themselves are insufficient.
We focus on failure bursts, since they have a large in-
fluence on the availability of the system. Previous litera-
ture on failure bursts has focused on methods for discov-
ering the relationship between the size of a failure event
and its probability of occurrence. In [10], the existence
of near-simultaneous failures in two large distributed sys-
tems is reported. The beta-binomial density and the bi-
exponential density are used to fit these distributions in
[6] and [24], respectively. In [24], the authors further
note that using an over-simplistic model for burst size,
for example a single size, could result in “dramatic inac-
curacies” in practical settings. On the other hand, even
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) 71
72
though the mean time to failure and mean time to recov-
ery of system nodes tend to be non-uniform and corre-
lated, this particular correlation effect has only a limited
impact on system-wide availability.
There is limited previous work on discovering patterns
of correlation in failures. The conditional probability of
failures for each pair of nodes in a system has been pro-
posed in [6] as a measure of correlation in the system.
This computation extends heuristically to sets of larger
nodes. A paradigm for discovering maximally indepen-
dent groups of nodes in a system to cope with correlated
failures is discussed in [34]. That paradigm involves col-
lecting failure statistics on each node in the system and
computing a measure of correlation, such as the mutual
information, between every pair of nodes. Both of these
approaches are computationally intensive and the results
found, unlike ours, are not used to build a predictive an-
alytical model for availability.
Models that have been developed to study the relia-
bility of long-term storage fall into two categories, non-
Markov and Markov models. Those in the first category
tend to be less versatile. For example, in [5] the prob-
ability of multiple faults occurring during the recovery
period of a stripe is approximated. Correlation is intro-
duced by means of a multiplicative factor that is applied
to the mean time to failure of a second chunk when the
first chunk is already unavailable. This approach works
only for stripes that are replicated and is not easily ex-
tendable to Reed-Solomon encoding. Moreover, the fac-
tor controlling time correlation is neither measurable nor
derivable from other data.
In [33], replication is compared with Reed-Solomon
with respect to storage requirement, bandwidth for write
and repair and disk seeks for reads. However, the com-
parison assumes that sweep and repair are performed at
regular intervals, as opposed to on demand.
Markov models are able to capture the system much
more generally and can be used to model both replication
and Reed-Solomon encoding. Examples include [21],
[32], [11] and [35]. However, these models all assume
independent failures of chunks. As we have shown, this
assumption potentially leads to overestimation of data
availability by many orders of magnitude. The authors
of [20] build a tool to optimize the disaster recovery ac-
cording to availability requirements, with similar goals
as our analysis of multi-cell replication. However, they
do not focus on studying the effect of failure characteris-
tics and data redundancy options.
Node availability in our environment is different from
previous work, such as [7, 18, 23], because we study a
large system that is tightly coupled in a single administra-
tive domain. These studies focus on measuring and pre-
dicting availability of individual desktop machines from
many, potentially untrusted, domains. Other authors
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
[11] studied data replication in face of failures, though
without considering availability of Reed-Solomon en-
codings or multi-cell replication.
10 Conclusions
We have presented data from Google’s clusters that char-
acterize the sources of failures contributing to unavail-
ability. We find that correlation among node failures
dwarfs all other contributions to unavailability in our pro-
duction environment.
In particular, though disks failures can result in per-
manent data loss, the multitude of transitory node fail-
ures account for most unavailability. We present a simple
time-window-based method to group failure events into
failure bursts which, despite its simplicity, successfully
identifies bursts with a common cause. We develop ana-
lytical models to reason about past and future availability
in our cells, including the effects of different choices of
replication, data placement and system parameters.
Inside Google, the analysis described in this paper has
provided a picture of data availability at a finer granu-
larity than previously measured. Using this framework,
we provide feedback and recommendations to the de-
velopment and operational engineering teams on differ-
ent replication and encoding schemes, and the primary
causes of data unavailability in our existing cells. Spe-
cific examples include:
e Determining the acceptable rate of successful trans-
fers to battery power for individual machines upon
a power outage.
e Focusing on reducing reboot times, because
planned kernel upgrades are a major source of cor-
related failures.
e Moving towards a dynamic delay before initiating
recoveries, based on failure classification and recent
history of failures in the cell.
Such analysis complements the intuition of the design-
ers and operators of these complex distributed systems.
Acknowledgments
Our findings would not have been possible without the
help of many of our colleagues. We would like to
thank the following people for their contributions to
data collection: Marc Berhault, Eric Dorland, Sangeetha
Eyunni, Adam Gee, Lawrence Greenfield, Ben Kochie,
and James O’ Kane. We would also like to thank a num-
ber of our colleagues for helping us improve the presen-
tation of these results. In particular, feedback from John
Wilkes, Tal Garfinkel, and Mike Marty was helpful. We
USENIX Association
would also like to thank our shepherd Bianca Schroeder
and the anonymous reviewers for their excellent feed-
back and comments, all of which helped to greatly im-
prove this paper.
References
[1]
[2]
[3]
[4]
[5]
[6]
[7]
[8]
[9]
[10]
[11]
[12]
USENIX Association
HDFS (Hadoop Distributed File System) architecture.
http://hadoop.apache.org/common/docs/
current/hdfs_design.html1, 2009.
ANDREAS, E. S., HAEBERLEN, A., DABEK, F., GON CHUN,
B., WEATHERSPOON, H., MORRIS, R., KAASHOEK, M. F.,
AND KUBIATOWICZ, J. Proactive replication for data durability.
In Proceedings of the 5th Intl Workshop on Peer-to-Peer Systems
(IPTPS) (2006).
BAIRAVASUNDARAM, L. N., GOODSON, G. R., PASUPATHY,
S., AND SCHINDLER, J. An analysis of latent sector errors
in disk drives. In SIGMETRICS ’07: Proceedings of the 2007
ACM SIGMETRICS International Conference on Measurement
and Modeling of Computer Systems (2007), pp. 289-300.
BAIRAVASUNDARAM, L. N., GOODSON, G. R., SCHROEDER,
B., ARPACI-DUSSEAU, A. C., AND ARPACI-DUSSEA, R. H.
An analysis of data corruption in the storage stack. In FAST ’08:
Proceedings of the 6th USENIX Conference on File and Storage
Technologies (2008), pp. 1-16.
BAKER, M., SHAH, M., ROSENTHAL, D. S. H., ROUSSOPOU-
Los, M., MANIATIS, P., GIULI, T., AND BUNGALE, P. A fresh
look at the reliability of long-term digital storage. In EuroSys ’06:
Proceedings of the Ist ACM SIGOPS/EuroSys European Confer-
ence on Computer Systems (2006), pp. 221-234.
BAKKALOGLU, M., WYLIE, J. J., WANG, C., AND GANGER,
G. R. Modeling correlated failures in survivable storage systems.
In Fast Abstract at International Conference on Dependable Sys-
tems & Networks (June 2002).
BOLosky, W. J., DOUCEUR, J. R., ELy, D., AND THEIMER,
M. Feasibility of a serverless distributed file system deployed on
an existing set of desktop PCs. In SIGMETRICS ’00: Proceed-
ings of the 2000 ACM SIGMETRICS international conference on
measurement and modeling of computer systems (2000), pp. 34—
43.
BROWN, D. M. The first passage time distribution for a paral-
lel exponential system with repair. In Reliability and fault tree
analysis (1974), Defense Technical Information Center.
CHANG, F., DEAN, J., GHEMAWAT, S., HSIEH, W. C., WAL-
LACH, D. A., BURROWS, M., CHANDRA, T., FIKES, A., AND
GRUBER, R. E. Bigtable: a distributed storage system for struc-
tured data. In OSDI ’06: Proceedings of the 7th Symposium
on Operating Systems Design and Implementation (Nov. 2006),
pp. 205-218.
CHARACTERISTICS, M. F., YALAG, P., NATH, S., YU, H.,
GIBBONS, P. B., AND SESHAN, S. Beyond availability: To-
wards a deeper understanding of machine failure characteristics
in large distributed systems. In WORLDS ’04: First Workshop on
Real, Large Distributed Systems (2004).
CHUN, B.-G., DABEK, F., HAEBERLEN, A., SIT, E., WEATH-
ERSPOON, H., KAASHOEK, M. F., KUBIATOWICZ, J., AND
MorkIs, R. Efficient replica maintenance for distributed stor-
age systems. In NSDI ’06: Proceedings of the 3rd conference on
Networked Systems Design & Implementation (2006), pp. 45-58.
CORBETT, P., ENGLISH, B., GOEL, A., GRCANAC, T.,
KLEIMAN, S., LEONG, J., AND SANKAR, S. Row-diagonal par-
ity for double disk failure correction. In FAST ’04: Proceedings
of the 3rd USENIX Conference on File and Storage Technologies
(2004), pp. 1-14.
13]
18]
[19]
[20]
[21]
[22]
[23]
[24]
[25]
[26]
[27]
[28]
[29]
[30]
EFRON, B., AND TIBSHIRANI, R. An Introduction to the Boot-
strap. Chapman and Hall, 1993.
| EPSTEIN, R. The Theory of Gambling and Statistical Logic. Aca-
demic Press, 1977.
FELLER, W. An Introduction to Probability Theory and Its Ap-
plication. John Wiley and Sons, 1968.
GHEMAWAT, S., GOBIOFF, H., AND LEUNG, S.-T. The Google
file system. In SOSP ’03: Proceedings of the 19th ACM Sympo-
sium on Operating Systems Principles (Oct. 2003), pp. 29-43.
ISARD, M. Autopilot: automatic data center management. ACM
SIGOPS Opererating Systems Review 41, 2 (2007), 60-67.
JAVADI, B., KONDO, D., VINCENT, J.-M., AND ANDERSON,
D. Mining for statistical models of availability in large-scale
distributed systems: An empirical study of SETI@home (2009).
pp. 1-10.
JIANG, W., Hu, C., ZHOU, Y., AND KANEVSKY, A. Are disks
the dominant contributor for storage failures?: a comprehensive
study of storage subsystem failure characteristics. In FAST ’08:
Proceedings of the 6th USENIX Conference on File and Storage
Technologies (2008), pp. 1-15.
KEETON, K., SANTOS, C., BEYER, D., CHASE, J., AND
WILKES, J. Designing for disasters. In FAST ’04: Proceedings
of the 3rd USENIX Conference on File and Storage Technologies
(2004), pp. 59-62.
LIAN, Q., CHEN, W., AND ZHANG, Z. On the impact of replica
placement to the reliability of distributed brick storage systems.
In ICDCS ’05: Proceedings of the 25th IEEE International Con-
ference on Distributed Computing Systems (2005), pp. 187-196.
McKusIck, M. K., AND QUINLAN, S. GFS: Evolution on fast-
forward. Communications of the ACM 53, 3 (2010), 42-49.
MICKENS, J. W., AND NOBLE, B. D. Exploiting availability
prediction in distributed systems. In NSDI ’06: Proceedings of
the 3rd conference on Networked Systems Design & Implementa-
tion (2006), pp. 73-86.
NATH, S., YU, H., GIBBONS, P. B., AND SESHAN, S. Sub-
tleties in tolerating correlated failures in wide-area storage sys-
tems. In NSDI ’06: Proceedings of the 3rd conference on Net-
worked Systems Design & Implementation (2006), pp. 225-238.
PINHEIRO, E., WEBER, W.-D., AND BARROSO, L. A. Failure
trends in a large disk drive population. In FAST ’07: Proceedings
of the 5th USENIX conference on File and Storage Technologies
(2007), pp. 17-23.
RAMABHADRAN, S., AND PASQUALE, J. Analysis of long-
running replicated systems. In INFOCOM 2006. 25th IEEE In-
ternational Conference on Computer Communications (2006),
pp. 1-9.
RESNICK, S. I. Adventures in stochastic processes. Birkhauser
Verlag, 1992.
RODRIGUES, R., AND LISKOV, B. High availability in DHTs:
Erasure coding vs. replication. In Proceedings of the 4th Interna-
tional Workshop on Peer-to-Peer Systems (2005).
SCHROEDER, B., AND GIBSON, G. A. Disk failures in the real
world: what does an MTTF of 1,000,000 hours mean to you? In
FAST ’07: Proceedings of the 5th USENIX conference on File
and Storage Technologies (2007), pp. 1-16.
SCHROEDER, B., PINHEIRO, E., AND WEBER, W.-D. DRAM
errors in the wild: a large-scale field study. In SIGMETRICS
’09: Proceedings of the Eleventh International Joint Conference
on Measurement and Modeling of Computer Systems (2009),
pp. 193-204.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) 73
74
[31]
[32]
[33]
[34]
[35]
SCHWARZ, T. J. E., XIN, Q., MILLER, E. L., LONG, D. D.E.,
Hospopor, A., AND NG, S. Disk scrubbing in large archival
storage systems. International Symposium on Modeling, Analy-
sis, and Simulation of Computer Systems (2004), 409-418.
T., S. Generalized Reed Solomon codes for erasure correction in
SDDS. In WDAS-4: Workshop on Distributed Data and Struc-
tures (2002).
WEATHERSPOON, H., AND KUBIATOWICZ, J. Erasure coding
vs. replication: A quantitative comparison. In IPTPS 01: Re-
vised Papers from the First International Workshop on Peer-to-
Peer Systems (2002), Springer-Verlag, pp. 328-338.
WEATHERSPOON, H., MOSCOVITZ, T., AND KUBIATOWICZ,
J. Introspective failure analysis: Avoiding correlated failures in
peer-to-peer systems. In SRDS ’02: Proceedings of the 21st IEEE
Symposium on Reliable Distributed Systems (2002), pp. 362-367.
XIN, Q., MILLER, E. L., SCHWARZ, T., LONG, D. D. E.,
BRANDT, S. A., AND LITWIN, W. Reliability mechanisms for
very large storage systems. In MSS ’03: Proceedings of the 20th
IEEE/ 11th NASA Goddard Conference on Mass Storage Systems
and Technologies (2003), pp. 146-156.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
USENIX Association
USENIX Association
Nectar: Automatic Management of Data and Computation in Datacenters
Pradeep Kumar Gunda, Lenin Ravindranath} Chandramohan A. Thekkath, Yuan Yu, Li Zhuang
Microsoft Research Silicon Valley
Abstract
Managing data and computation is at the heart of data-
center computing. Manual management of data can lead
to data loss, wasteful consumption of storage, and labo-
rious bookkeeping. Lack of proper management of com-
putation can result in lost opportunities to share common
computations across multiple jobs or to compute results
incrementally.
Nectar is a system designed to address the aforemen-
tioned problems. It automates and unifies the manage-
ment of data and computation within a datacenter. In
Nectar, data and computation are treated interchange-
ably by associating data with its computation. De-
rived datasets, which are the results of computations, are
uniquely identified by the programs that produce them,
and together with their programs, are automatically man-
aged by a datacenter wide caching service. Any derived
dataset can be transparently regenerated by re-executing
its program, and any computation can be transparently
avoided by using previously cached results. This en-
ables us to greatly improve datacenter management and
resource utilization: obsolete or infrequently used de-
rived datasets are automatically garbage collected, and
shared common computations are computed only once
and reused by others.
This paper describes the design and implementation of
Nectar, and reports on our evaluation of the system using
analytic studies of logs from several production clusters
and an actual deployment on a 240-node cluster.
1 Introduction
Recent advances in distributed execution engines (Map-
Reduce [7], Dryad [18], and Hadoop [12]) and high-level
language support (Sawzall [25], Pig [24], BOOM [3],
HIVE [17], SCOPE [6], DryadLINQ [29]) have greatly
*L. Ravindranath is affiliated with the Massachusetts Institute of
Technology and was a summer intern on the Nectar project.
simplified the development of large-scale, data-intensive,
distributed applications. However, major challenges still
remain in realizing the full potential of data-intensive
distributed computing within datacenters. In current
practice, a large fraction of the computations in a dat-
acenter is redundant and many datasets are obsolete or
seldom used, wasting vast amounts of resources in a dat-
acenter.
As one example, we quantified the wasted storage in
our 240-node experimental Dryad/DryadLINQ cluster.
We crawled this cluster and noted the last access time
for each data file. We discovered that around 50% of the
files was not accessed in the last 250 days.
As another example, we examined the execution statis-
tics of 25 production clusters running data-parallel ap-
plications. We estimated that, on one such cluster, over
7000 hours of redundant computation can be eliminated
per day by caching intermediate results. (This is approx-
imately equivalent to shutting off 300 machines daily.)
Cumulatively, over all clusters, this figure is over 35,000
hours per day.
Many of the resource issues in a datacenter arise due
to lack of efficient management of either data or compu-
tation, or both. This paper describes Nectar: a system
that manages the execution environment of a datacenter
and is designed to address these problems.
A key feature of Nectar is that it treats data and com-
putation in a datacenter interchangeably in the following
sense. Data that has not been accessed for a long pe-
riod may be removed from the datacenter and substituted
by the computation that produced it. Should the data be
needed in the future, the computation is rerun. Similarly,
instead of executing a user’s program, Nectar can par-
tially or fully substitute the results of that computation
with data already present in the datacenter. Nectar relies
on certain properties of the programming environment
in the datacenter to enable this interchange of data and
computation.
Computations running on a Nectar-managed datacen-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) 75
76
ter are specified as programs in LINQ [20]. LINQ com-
prises a set of operators to manipulate datasets of NET
objects. These operators are integrated into high level
.NET programming languages (e.g., C#), giving pro-
grammers direct access to .NET libraries as well tradi-
tional language constructs such as loops, classes, and
modules. The datasets manipulated by LINQ can contain
objects of an arbitrary .NET type, making it easy to com-
pute with complex data such as vectors, matrices, and
images. All of these operators are functional: they trans-
form input datasets to new output datasets. This property
helps Nectar reason about programs to detect program
and data dependencies. LINQ is a very expressive and
flexible language, e.g., the MapReduce class of compu-
tations can be trivially expressed in LINQ.
Data stored in a Nectar-managed datacenter are di-
vided into one of two classes: primary or derived. Pri-
mary datasets are created once and accessed many times.
Derived datasets are the results produced by computa-
tions running on primary and other derived datasets. Ex-
amples of typical primary datasets in our datacenters
are click and query logs. Examples of typical derived
datasets are the results of thousands of computations per-
formed on those click and query logs.
In a Nectar-managed datacenter, all access to a derived
dataset is mediated by Nectar. At the lowest level of the
system, a derived dataset is referenced by the LINQ pro-
gram fragment or expression that produced it. Program-
mers refer to derived datasets with simple pathnames that
contain a simple indirection (much like a UNIX symbolic
link) to the actual LINQ programs that produce them. By
maintaining this mapping between a derived dataset and
the program that produced it, Nectar can reproduce any
derived dataset after it is automatically deleted. Primary
datasets are referenced by conventional pathnames, and
are not automatically deleted.
A Nectar-managed datacenter offers the following ad-
vantages.
1. Efficient space utilization. Nectar implements a
cache server that manages the storage, retrieval, and
eviction of the results of all computations (i.e., de-
rived datasets). As well, Nectar retains the de-
scription of the computation that produced a de-
rived dataset. Since programmers do not directly
manage datasets, Nectar has considerable latitude
in optimizing space: it can remove unused or in-
frequently used derived datasets and recreate them
on demand by rerunning the computation. This is a
classic trade-off of storage and computation.
2. Reuse of shared sub-computations. Many appli-
cations running in the same datacenter share com-
mon sub-computations. Since Nectar automatically
caches the results of sub-computations, they will be
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
computed only once and reused by others. This sig-
nificantly reduces redundant computations, result-
ing in better resource utilization.
3. Incremental computations. Many datacenter ap-
plications repeat the same computation on a slid-
ing window of an incrementally augmented dataset.
Again, caching in Nectar enables us to reuse the re-
sults of old data and only compute incrementally for
the newly arriving data.
4. Ease of content management. With derived datasets
uniquely named by LINQ expressions, and auto-
matically managed by Nectar, there is little need for
developers to manage their data manually. In par-
ticular, they do not have to be concerned about re-
membering the location of the data. Executing the
LINQ expression that produced the data is sufficient
to access the data, and incurs negligible overhead in
almost all cases because of caching. This is a sig-
nificant advantage because most datacenter applica-
tions consume a large amount of data from diverse
locations and keeping track of the requisite filepath
information is often a source of bugs.
Our experiments show that Nectar, on average, could
improve space utilization by at least 50%. As well, in-
cremental and sub-computations managed by Nectar pro-
vide an average speed up of 30% for the programs run-
ning on our clusters. We provide a detailed quantitative
evaluation of the first three benefits in Section 4. We
have not done a detailed user study to quantify the fourth
benefit, but the experience from our initial deployment
suggests that there is evidence to support the claim.
Some of the techniques we used such as dividing
datasets into primary and derived and reusing the re-
sults of previous computations via caching are reminis-
cent of earlier work in version management systems [15],
incremental database maintenance [5], and functional
caching [16, 27]. Section 5 provides a more detailed
analysis of our work in relation to prior research.
This paper makes the following contributions to the
literature:
e We propose a novel and promising approach that
automates and unifies the management of data and
computation in a datacenter, leading to substantial
improvements in datacenter resource utilization.
e We present the design and implementation of our
system, including a sophisticated program rewriter
and static program dependency analyzer.
e We present a systematic analysis of the performance
of our system from a real deployment on 240-nodes
as well as analytical measurements.
USENIX Association
USENIX Association
DryadLINQ Program
DryadLINQ/Dryad
Nectar Program Store | Distributed FS Nectar Data Store
Figure 1: Nectar architecture. The system consists of a
client-side library and cluster-wide services. Nectar re-
lies on the services of DryadLINQ/Dryad and TidyFS, a
distributed file system.
The rest of this paper is organized as follows. Sec-
tion 2 provides a high-level overview of the Nectar sys-
tem. Section 3 describes the implementation of the sys-
tem. Section 4 evaluates the system using real work-
loads. Section 5 covers related work and Section 6 dis-
cusses future work and concludes the paper.
2 System Design Overview
The overall Nectar architecture is shown in Figure 1.
Nectar consists of a client-side component that runs on
the programmer’s desktop, and two services running in
the datacenter.
Nectar is completely transparent to user programs and
works as follows. It takes a DryadLINQ program as in-
put, and consults the cache service to rewrite it to an
equivalent, more efficient program. Nectar then hands
the resulting program to DryadLINQ which further com-
piles it into a Dryad computation running in the clus-
ter. At run time, a Dryad job is a directed acyclic graph
where vertices are programs and edges represent data
channels. Vertices communicate with each other through
data channels. The input and output of a DryadLINQ
program are expected to be streams. A stream consists of
an ordered sequence of extents, each storing a sequence
of object of some data type. We use an in-house fault-
tolerant, distributed file system called TidyFS to store
streams.
Nectar makes certain assumptions about the underly-
ing storage system. We require that streams be append-
only, meaning that new contents are added by either ap-
pending to the last extent or adding a new extent. The
metadata of a stream contains Rabin fingerprints [4] of
the entire stream and its extents.
Nectar maintains and manages two namespaces in
TidyFS. The program store keeps all DryadLINQ pro-
grams that have ever executed successfully. The data
store is used to store all derived streams generated by
DryadLINQ programs. The Nectar cache server pro-
vides cache hits to the program rewriter on the client
side. It also implements a replacement policy that deletes
cache entries of least value. Any stream in the data
store that is not referenced by any cache entry is deemed
to be garbage and deleted permanently by the Nectar
garbage collector. Programs in the program store are
never deleted and are used to recreate a deleted derived
stream if it is needed in the future.
A simple example of a program is shown in Ex-
ample 2.1. The program groups identical words in a
large document into groups and applies an arbitrary user-
defined function Reduce to each group. This is a typ-
ical MapReduce program. We will use it as a running
example to describe the workings of Nectar. TidyFS,
Dryad, and DryadLINQ are described in detail else-
where [8, 18, 29]. We only discuss them briefly below
to illustrate their relationships to our system.
In the example, we assume that the input D is a large
(replicated) dataset partitioned as D,, D2 ... D, in the
TidyFS distributed file system and it consists of lines of
text. SelectMany is a LINQ operator, which first pro-
duces a single list of output records for each input record
and then “flattens” the lists of output records into a sin-
gle list. In our example, the program applies the function
x => x.Split(’ ’) to each line in D to produce
the list of words in D.
The program then uses the GroupBy operator to
group the words into a list of groups, putting the same
words into a single group. GroupBy takes a key-selector
function as the argument, which when applied to an
input record returns a collating “key” for that record.
Group By applies the key-selector function to each input
record and collates the input into a list of groups (multi-
sets), one group for all the records with the same key.
The last line of the program applies a transforma-
tion Reduce to each group. Select is a simpler ver-
sion of SelectMany. Unlike the latter, Select pro-
duces a single output record (determined by the function
Reduce) for each input record.
Example 2.1 A typical MapReduce job expressed in
LINQ. (x => x.Split(’ ’)) produces a list of
blank-separated words; (x => x) produces a key for
each input; Reduce is an arbitrary user supplied func-
tion that is applied to each input.
words = D.SelectMany(x => x.Split(’ ’));
groups = words.GroupBy (x => x);
result = groups.Select (x => Reduce (x) );
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) 77
78
Figure 2: Execution graph produced by Nectar given
the input LINQ program in Example 2.1. The nodes
named SM+D executes Se lect Many and distributes the
results. GB+S executes GroupBy and Select.
When the program in Example 2.1 is run for the first
time, Nectar, by invoking DryadLINQ, produces the dis-
tributed execution graph shown in Figure 2, which is then
handed to Dryad for execution. (For simplicity of exposi-
tion, we assume for now that there are no cache hits when
Nectar rewrites the program.) The SM+D vertex performs
the SelectMany and distributes the results by parti-
tioning them on a hash of each word. This ensures that
identical words are destined to the same GB+S vertex
in the graph. The GB+S vertex performs the GroupBy
and Select operations together. The AE vertex adds a
cache entry for the final result of the program. Notice
that the derived stream created for the cache entry shares
the same set of extents with the result of the computa-
tion. So, there is no additional cost of storage space. As
a rule, Nectar always creates a cache entry for the final
result of a computation.
2.1 Client-Side Library
On the client side, Nectar takes advantage of cached re-
sults from the cache to rewrite a program P to an equiv-
alent, more efficient program P’. It automatically inserts
AddEntry calls at appropriate places in the program so
new cache entries can be created when P’ is executed.
The AddEntry calls are compiled into Dryad vertices that
create new cache entries at runtime. We summarize the
two main client-side components below.
Cache Key Calculation
A computation is uniquely identified by its program
and inputs. We therefore use the Rabin fingerprint of
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
the program and the input datasets as the cache key for
a computation. The input datasets are stored in TidyFS
and their fingerprints are calculated based on the actual
stream contents. Nectar calculates the fingerprint of the
program and combines it with the fingerprints of the in-
put datasets to form the cache key.
The fingerprint of a DryadLINQ program must be able
to detect any changes to the code the program depends
on. However, the fingerprint should not change when
code the program does not depend on changes. This
is crucial for the correctness and practicality of Nectar.
(Fingerprints can collide but the probability of a colli-
sion can be made vanishingly small by choosing long
enough fingerprints.) We implement a static dependency
analyzer to compute the transitive closure of all the code
that can be reached from the program. The fingerprint is
then formed using all reachable code. Of course, our an-
alyzer only produces an over-approximation of the true
dependency.
Rewriter
Nectar rewrites user programs to use cached results
where possible. We might encounter different entries
in the cache server with different sub-expressions and/or
partial input datasets. So there are typically multiple al-
ternatives to choose from in rewriting a DryadLINQ pro-
gram. The rewriter uses a cost estimator to choose the
best one from multiple alternatives (as discussed in Sec-
tion 3.1).
Nectar supports the following two rewriting scenarios
that arise very commonly in practice.
Common sub-expressions. Internally, a DryadLINQ
program is represented as a LINQ expression tree. Nec-
tar treats all prefix sub-expressions of the expression tree
as candidates for caching and looks up in the cache for
possible cache hits for every prefix sub-expression.
Incremental computations. Incremental computation
on datasets is a common occurrence in data intensive
computing. Typically, a user has run a program P on in-
put D. Now, he is about to compute P on input D + D’,
the concatenation of D and D’. The Nectar rewriter finds
a new operator to combine the results of computing on
the old input and the new input separately. See Sec-
tion 2.3 for an example.
A special case of incremental computation that occurs
in datacenters is a computation that executes on a sliding
window of data. That is, the same program is repeatedly
run on the following sequence of inputs:
Input, = dy + dy a dn,
Input = dy + dz sce FE dn+15
Inputs = dz + da + saa + dn+2;
USENIX Association
USENIX Association
Here d; is a dataset that (potentially) consists of mul-
tiple extents distributed over many computers. So suc-
cessive inputs to the program (Jnput,;) are datasets with
some old extents removed from the head of the previous
input and new extents appended to the tail of it. Nec-
tar generates cache entries for each individual dataset d;,
and can use them in subsequent computations.
In the real world, a program may belong to a combina-
tion of the categories above. For example, an application
that analyzes logs of the past seven days is rewritten as
an incremental computation by Nectar, but Nectar may
use sub-expression results of log preprocessing on each
day from other applications.
2.2 Datacenter-Wide Service
The datacenter-wide service in Nectar comprises two
separate components: the cache service and the garbage
collection service. The actual datasets are stored in
the distributed storage system and the datacenter-wide
services manipulate the actual datasets by maintaining
pointers to them.
Cache Service
Nectar implements a distributed datacenter-wide
cache service for bookkeeping information about Dryad-
LINQ programs and the location of their results. The
cache service has two main functionalities: (1) serving
the cache lookup requests by the Nectar rewriter; and (2)
managing derived datasets by deleting the cache entries
of least value.
Programs of all successful computations are uploaded
to a dedicated program store in the cluster. Thus, the
service has the necessary information about cached re-
sults, meaning that it has a recipe to recreate any de-
rived dataset in the datacenter. When a derived dataset
is deleted but needed in the future, Nectar recreates it us-
ing the program that produced it. If the inputs to that
program have themselves been deleted, it backtracks re-
cursively till it hits the immutable primary datasets or
cached derived datasets. Because of this ability to recre-
ate datasets, the cache server can make informed deci-
sions to implement a cache replacement policy, keeping
the cached results that yield the most hits and deleting the
cached results of less value when storage space is low.
Garbage Collector
The Nectar garbage collector operates transparently to
the users of the cluster. Its main job is to identify datasets
unreachable from any cache entry and delete them. We
use a standard mark-and-sweep collector. Actual content
deletion is done in the background without interfering
with the concurrent activities of the cache server and job
executions. Section 3.2 has additional detail.
Figure 3: Execution graph produced by Nectar on the
program in Example 2.1 after it elects to cache the results
of computations. Notice that the GroupBy and Select
are now encapsulated in separate nodes. The new AE
vertex creates a cache entry for the output of GroupBy.
2.3 Example: Program Rewriting
Let us look at the interesting case of incremental compu-
tation by continuing Example 2.1.
After the program has been executed a sufficient num-
ber of times, Nectar may elect to cache results from some
of its subcomputations based on the usage information
returned to it from the cache service. So subsequent runs
of the program may cause Nectar to create different exe-
cution graphs than those created previously for the same
program. Figure 3 shows the new execution graph when
Nectar chooses to cache the result of GroupBy (c.f. Fig-
ure 2). It breaks the pipeline of GroupBy and Select
and creates an additional AddEntry vertex (denoted by
AE) to cache the result of GroupBy. During the exe-
cution, when the GB stage completes, the AE vertex will
run, creating a new TidyFS stream and a cache entry for
the result of GroupBy. We denote the stream by Gp,
partitioned as Gip,, Gp,, .. Gp,.
Subsequently, assume the program in Example 2.1 is
run on input (D + X), where X is a new dataset parti-
tioned as Xj, X2,.. X;. The Nectar rewriter would get a
cache hit on Gp. So it only needs to perform GroupBy
on X and merge with G'p to form new groups. Figure 4
shows the new execution graph created by Nectar.
There are some subtleties involved in the rewriting
process. Nectar first determines that the number of par-
titions (n) of Gp. It then computes GroupBy on X the
same way as Gp, generating n partitions with the same
distribution scheme using the identical hash function as
was used previously (see Figures 2 and 3). That is, the
rewritten execution graph has k; SM+D vertices, but n GB
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) 79
80
vertices. The MG vertex then performs a pairwise merge
of the output GB with the cached result Gp. The result
of MG is again cached for future uses, because Nectar
notices the pattern of incremental computation and ex-
pects that the same computation will happen on datasets
of form Gp+x+y in the future.
Figure 4: The execution graph produced by Nectar on
the program in Example 2.1 on the dataset D + X. The
dataset X consists of / partitions. The MG vertex merges
groups with the same key. Both the results of GB and MG
are cached. There are k SM+D vertices, but n GB, MG,
and S vertices. Gp1,...,Gpn are the partitions of the
cached result.
Similar to MapReduce’s combiner optimization [7]
and Data Cube computation [10], DryadLINQ can de-
compose Reduce into the composition of two associa-
tive and commutative functions if Reduce is determined
to be decomposable. We handle this by first applying the
decomposition as in [28] and then the caching and rewrit-
ing as described above.
3 Implementation Details
We now present the implementation details of the two
most important aspects of Nectar: Section 3.1 describes
computation caching and Section 3.2 describes the auto-
matic management of derived datasets.
3.1 Caching Computations
Nectar rewrites a DryadLINQ program to an equivalent
but more efficient one using cached results. This gen-
erally involves: 1) identifying all sub-expressions of the
expression, 2) probing the cache server for all cache hits
for the sub-expressions, 3) using the cache hits to rewrite
the expression into a set of equivalent expressions, and 4)
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
choosing one that gives us the maximum benefit based on
some cost estimation.
Cache and Programs
A cache entry records the result of executing a pro-
gram on some given input. (Recall that a program may
have more than one input depending on its arity.) The
entry is of the form:
(F Ppp, F'Pp, Result, Statistics, F PList)
Here, F’Ppp is the combined fingerprint of the pro-
gram and its input datasets, Pp is the fingerprint of the
program only, Result is the location of the output, and
Statistics contains execution and usage information of
this cache entry. The last field F'PList contains a list
of fingerprint pairs each representing the fingerprints of
the first and last extents of an input dataset. We have one
fingerprint pair for every input of the program. As we
shall see later, it is used by the rewriter to search amongst
cache hits efficiently. Since the same program could have
been executed on different occasions on different inputs,
there can be multiple cache entries with the same Ff’ Pp.
We use F'Ppp as the primary key. So our caching
is sound only if F’'Ppp can uniquely determine the re-
sult of the computation. The fingerprint of the inputs is
based on the actual content of the datasets. The finger-
print of a dataset is formed by combining the fingerprints
of its extents. For a large dataset, the fingerprints of its
extents are efficiently computed in parallel by the data-
center computers.
The computation of the program fingerprint is tricky,
as the program may contain user-defined functions that
call into library code. We implemented a static depen-
dency analyzer to capture all dependencies of an ex-
pression. At the time a DryadLINQ program is in-
voked, DryadLINQ knows all the dynamic linked li-
braries (DLLs) it depends on. We divide them into two
categories: system and application. We assume system
DLLs are available and identical on all cluster machines
and therefore are not included in the dependency. For
an application DLL that is written in native code (e.g.,
C or assembler), we include the entire DLL as a depen-
dency. For soundness, we assume that there are no call-
backs from native to managed code. For an application
DLL that is in managed code (e.g., C#), our analyzer tra-
verses the call graph to compute all the code reachable
from the initial expression.
The analyzer works at the bytecode level. It uses stan-
dard .NET reflection to get the body of a method, finds
all the possible methods that can be called in the body,
and traverses those methods recursively. When a virtual
method call is encountered, we include all the possible
call sites. While our analysis is certainly a conservative
approximation of the true dependency, it is reasonably
USENIX Association
USENIX Association
precise and works well in practice. Since dynamic code
generation could introduce unsoundness into the analy-
sis, it is forbidden in managed application DLLs, and is
statically enforced by the analyzer.
The statistics information kept in the cache entry is
used by the rewriter to find an optimal execution plan. It
is also used to implement the cache insertion and eviction
policy. It contains information such as the cumulative ex-
ecution time, the number of hits on this entry, and the last
access time. The cumulative execution time is defined as
the sum of the execution time of all upstream Dryad ver-
tices of the current execution stage. It is computed at the
time of the cache entry insertion using the execution logs
generated by Dryad.
The cache server supports a simple client interface.
The important operations include: (1) Lookup (fp)
finds and returns the cache entry that has fp as the pri-
mary key (F' Ppp); (2) Inquire (fp) returns all cache
entries that have fp as their F’'Pp; and (3) AddEntry
inserts a new cache entry. We will see their uses in the
following sections.
The Rewriting Algorithm
Having explained the structure and interface of the
cache, let us now look at how Nectar rewrites a program.
For a given expression, we may get cache hits on
any possible sub-expression and subset of the input
dataset, and considering all of them in the rewriting
is not tractable. We therefore only consider cache
hits on prefix sub-expressions on segments of the input
dataset. More concretely, consider a simple example
D.Where (P) .Select (F). The Where operator ap-
plies a filter to the input dataset D, and the Select op-
erator applies a transformation to each item in its input.
We will only consider cache hits for the sub-expressions
S.Where(P) andS.Where (P) .Select (F) where
S is a subsequence of extents in D.
Our rewriting algorithm is a simple recursive proce-
dure. We start from the largest prefix sub-expression, the
entire expression. Below is an outline of the algorithm.
For simplicity of exposition, we assume that the expres-
sions have only one input.
Step 1. For the current sub-expression FE’, we probe the
cache server to obtain all the possible hits on it. There
can be multiple hits on different subsequences of the in-
put D. Let us denote the set of hits by H. Note that each
hit also gives us its saving in terms of cumulative exe-
cution time. If there is a hit on the entire input D, we use
that hit and terminate because it gives us the most sav-
ings in terms of cumulative execution time. Otherwise
we execute Steps 2-4.
Step 2. We compute the best execution plan for F using
hits on its smaller prefixes. To do that, we first compute
the best execution plan for each immediate successor
prefix of & by calling our procedure recursively, and
then combine them to form a single plan for &. Let us
denote this plan by (P,,C1) where C; is its saving in
terms of cumulative execution time.
Step 3. For the H hits on & (from Step 1), we choose
a subset of them such that (a) they operate on disjoint
subsequence of D, and (b) they give us the most saving
in terms of cumulative execution time. This boils down
to the well-known problem of computing the maxi-
mum independent sets of an interval graph, which has
a known efficient solution using dynamic programming
techniques [9]. We use this subset to form another ex-
ecution plan for EF on D. Let us denote this plan by
(P2, C2).
Step 4. The final execution plan is the one from P,; and
P, that gives us more saving.
In Step 1, the rewriter calls Inquire to compute H.
As described before, Inquire returns all the possible
cache hits of the program with different inputs. A useful
hit means that its input dataset is identical to a subse-
quence of extents of D. A brute force search is inefficient
and requires to check every subsequence. As an opti-
mization, we store in the cache entry the fingerprints of
the first and last extents of the input dataset. With that
information, we can compute #7 in linear time.
Intuitively, in rewriting a program P on incremental
data Nectar tries to derive a combining operator C’ such
that P(D+D’) = C(P(D), D’), where C combines the
results of P on the datasets D and D’. Nectar supports
all the LINQ operators DryadLINQ supports.
The combining functions for some LINQ opera-
tors require the parallel merging of multiple streams,
and are not directly supported by DryadLINQ. We
introduced three combining functions: MergeSort,
HashMergeGroups, and SortMergeGroups,
which are straightforward to implement using Dryad-
LINQ’s Apply operator [29]. MergeSort takes
multiple sorted input streams, and merge sorts them.
HashMergeGroups and SortMergeGroups take
multiple input streams and merge groups of the same
key from the input streams. If all the input streams are
sorted, Nectar chooses to use SortMergeGroups,
which is streaming and more efficient. Otherwise,
Nectar uses HashMergeGroups. The MG vertex in
Figure 4 is an example of this group merge.
The technique of reusing materialized views in
database systems addresses a similar problem. One im-
portant difference is that a database typically does not
maintain views for multiple versions of a table, which
would prevent it from reusing results computed on old
incarnations of the table. For example, suppose we have
a materialized view V on D. When D is changed to
D + Dyj, the view is also updated to V’. So for any fu-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) 81
82
ture computation on D + Dg, V is no longer available
for use. In contrast, Nectar maintains both V and V’, and
automatically tries to reuse them for any computation, in
particular the ones on D + Do.
Cache Insertion Policy
We consider every prefix sub-expression of an expres-
sion to be a candidate for caching. Adding a cache entry
incurs additional cost if the entry is not useful. It requires
us to store the result of the computation on disk (instead
of possibly pipelining the result to the next stage), incur-
ring the additional disk IO and space overhead. Obvi-
ously it is not practical to cache everything. Nectar im-
plements a simple strategy to determine what to cache.
First of all, Nectar always creates a cache entry for
the final result of a computation as we get it for free: it
does not involve a break of the computation pipeline and
incurs no extra IO and space overhead.
For sub-expression candidates, we wish to cache them
only when they are predicted to be useful in the future.
However, determining the potential usefulness of a cache
entry is generally difficult. So we base our cache inser-
tion policy on heuristics. The caching decision is made
in the following two phases.
First, when the rewriter rewrites an expression, it de-
cides on the places in the expression to insert AddEntry
calls. This is done using the usage statistics maintained
by the cache server. The cache server keeps statistics for
a sub-expression based on request history from clients.
In particular, it records the number of times it has been
looked up. On response to a cache lookup, this number
is included in the return value. We insert an AddEntry
call for an expression only when the number of lookups
on it exceeds a predefined threshold.
Second, the decision made by the rewriter may still be
wrong because of the lack of information about the sav-
ing of the computation. Information such as execution
time and disk consumption are only available at run time.
So the final insertion decision is made based on the run-
time information of the execution of the sub-expression.
Currently, we use a simple benefit function that is propor-
tional to the execution time and inversely proportional to
storage overhead. We add the cache entry when the ben-
efit exceeds a threshold.
We also make our cache insertion policy adaptive to
storage space pressure. When there is no pressure, we
choose to cache more aggressively as long as it saves
machine time. This strategy could increase the useless
cache entries in the cache. But it is not a problem because
it is addressed by Nectar’s garbage collection, discussed
further below.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
3.2 Managing Derived Data
Derived datasets can take up a significant amount of stor-
age space in a datacenter, and a large portion of it could
be unused or seldom used. Nectar keeps track of the us-
age statistics of all derived datasets and deletes the ones
of the least value. Recall that Nectar permanently stores
the program of every derived dataset so that a deleted de-
rived can be recreated by re-running its program.
Data Store for Derived Data
As mentioned before, Nectar stores all derived
datasets in a data store inside a distributed, fault-tolerant
file system. The actual location of a derived dataset is
completely opaque to programmers. Accessing an ex-
isting derived dataset must go through the cache server.
We expose a standard file interface with one important
restriction: New derived datasets can only be created as
results of computations.
P = q.ToTable(“lenin/foo.pt”)
Pp Nectar Cluster-Wide Services
Cache Server
FP(P)
iter
Poin;
DryadLINQ/Dryad
fe
OD
ls?
Ai
iS
Reference
Vv
lenin/foo.pt
FP(P) Distributed FS
A31E4.pt
Nectar Data Store
Figure 5: The creation of a derived dataset. The actual
dataset is stored in the Nectar data store. The user file
contains only the primary key of the cache entry associ-
ated with the derived.
Our scheme to achieve this is straightforward. Fig-
ure 5 shows the flow of creating a derived dataset by a
computation and the relationship between the user file
and the actual derived dataset. In the figure, P is a user
program that writes its output to lenin/foo.pt. Af-
ter applying transformations by Nectar and DryadLINQ,
it is executed in the datacenter by Dryad. When the ex-
ecution succeeds, the actual derived dataset is stored in
the data store with a unique name generated by Nectar. A
cache entry is created with the fingerprint of the program
(FP (P)) as the primary key and the unique name as a
field. The content of lenin/foo.pt just contains the
primary key of the cache entry.
To access lenin/foo.pt, Nectar simply uses
FP (P) to look up the cache to obtain the location of
the actual derived dataset (A31E4 .pt). The fact that all
accesses go through the cache server allows us to keep
USENIX Association
USENIX Association
track of the usage history of every derived dataset and
to implement automatic garbage collection for derived
datasets based on their usage history.
Garbage Collection
When the available disk space falls below a thresh-
old, the system automatically deletes derived datasets
that are considered to be least useful in the future. This
is achieved by a combination of the Nectar cache server
and garbage collector.
A derived dataset is protected from garbage collection
if it is referenced by any cache entry. So, the first step
is to evict from the cache, entries that the cache server
determines to have the least value.
The cache server uses information stored in the cache
entries to do a cost-benefit analysis to determine the use-
fulness of the entries. For each cache entry, we keep
track of the size of the resulting derived dataset (5), the
elapsed time since it was last used (AT), the number of
times (JV) it has been used and the cumulative machine
time (M) of the computation that created it. The cache
server uses these values to compute the cost-to-benefit
ratio
CBRatio = (S x AT)/(N x M)
of each cache entry and deletes entries that have the
largest ratios so that the cumulative space saving reaches
a predefined threshold.
Freshly created cache entries do not contain informa-
tion for us to compute a useful cost/benefit ratio. To give
them a chance to demonstrate their usefulness, we ex-
clude them from deletion by using a lease on each newly
created cache entry.
The entire cache eviction operation is done in the
background, concurrently with any other cache server
operations. When the cache server completes its evic-
tion, the garbage collector deletes all derived datasets
not protected by a cache entry using a simple mark-and-
sweep algorithm. Again, this is done in the background,
concurrently with any other activities in the system.
Other operations can run concurrently with the
garbage collector and create new cache entries and de-
rived datasets. Derived datasets pointed to by cache en-
tries (freshly created or otherwise) are not candidates for
garbage collection. Notice however that freshly created
derived datasets, which due to concurrency may not yet
have a cache entry, also need to protected from garbage
collection. We do this with a lease on the dataset.
With these leases in place, garbage collection is quite
straightforward. We first compute the set of all derived
datasets (ignoring the ones with unexpired leases) in our
data store, exclude from it the set of all derived datasets
referenced by cache entries, and treat the remaining as
garbage.
Our system could mistakenly delete datasets that are
subsequently requested, but these can be recreated by re-
executing the appropriate program(s) from the program
store. Programs are stored in binary form in the pro-
gram store. A program is a complete Dryad job that can
be submitted to the datacenter for execution. In particu-
lar, it includes the execution plan and all the application
DLLs. We exclude all system DLLs, assuming that they
are available on the datacenter machines. For a typical
datacenter that runs 1000 jobs daily, our experience sug-
gests it would take less than 1TB to store one year’s pro-
gram (excluding system DLLs) in uncompressed form.
With compression, it should take up roughly a few hun-
dreds of gigabytes of disk space, which is negligible even
for a small datacenter.
4 Experimental Evaluation
We evaluate Nectar running on our 240-node research
cluster as well as present analytic results of execution
logs from 25 large production clusters that run jobs sim-
ilar to those on our research cluster. We first present our
analytic results.
4.1 Production Clusters
We use logs from 25 different clusters to evaluate the
usefulness of Nectar. The logs consist of detailed execu-
tion statistics for 33182 jobs in these clusters for a recent
3-month period. For each job, the log has the source pro-
gram and execution statistics such as computation time,
bytes read and written and the actual time taken for ev-
ery stage in a job. The log also gives information on the
submission time, start time, end time, user information,
and job status.
Programs from the production clusters work with mas-
sive datasets such as click logs and search logs. Programs
are written in a language similar to DryadLINQ in that
each program is a sequence of SQL-like queries [6]. A
program is compiled into an expression tree with various
stages and modeled as a DAG with vertices representing
processes and edges representing data flows. The DAGs
are executed on a Dryad cluster, just as in our Nectar
managed cluster. Input data in these clusters is stored as
append-only streams.
Benefits from Caching
We parse the execution logs to recreate a set of DAGs,
one for each job. The root of the DAG represents the
input to the job and a path through the DAG starting at
the root represents a partial (i.e., a sub-) computation of
the job. Identical DAGs from different jobs represent an
opportunity to save part of the computation time of a later
job by caching results from the earlier ones. We simulate
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) 83
84
the effect of Nectar’s caching on these DAGs to estimate
cache hits.
Our results show that on average across all clusters,
more than 35% of the jobs could benefit from caching.
More than 30% of programs in 18 out of 25 clusters
could have at least one cache hit, and there were even
some clusters where 65% of programs could have cache
hits.
The log contains detailed computation time informa-
tion for each node in the DAG for a job. When there is
a cache hit on a sub-computation of a job, we can there-
fore calculate the time saved by the cache hit. We show
the result of this analysis in two different ways: Figure 6
shows the percentage of computing time saved and Ta-
ble 1 shows the minimum number of hours of computa-
tion saved in each cluster.
Figure 6 shows that significant percentage of computa-
tion time can be saved in each cluster with Nectar. Most
clusters can save a minimum of 20% to 40% of com-
putation time and in some clusters the savings are up to
50%. Also, as an example, Table 1 shows a minimum of
7143 hours of computation per day can be saved using
Nectar in Cluster C5. This is roughly equivalent to say-
ing that about 300 machines in that cluster were doing
wasteful computations all day that caching could elimi-
nate. Across all 25 clusters, 35078 hours of computation
per day can be saved, which is roughly equivalent to sav-
ing 1461 machines.
8
NNtMNON OD aamMt
GVOVOVVV0OVCRF HR nN ena As ANNAN
OCUV00
Figure 6: Fraction of compute time saved in each cluster
0.7
Fraction of computation time saved
Co Oo Oo Oo Oo
n ww B in a
9
iB
0 i
4 +
9 9
Cluster
Ease of Program Development
Our analysis of the caching accounted for both sub-
computation as well as incremental/sliding window hits.
We noticed that the percentage of sliding window hits in
some production clusters was minimal (under 5%). We
investigated this further and noticed that many program-
mers explicitly structured their programs so that they can
reuse a previous computation. This somewhat artificial
structure makes their programs cumbersome, which can
be alleviated by using Nectar.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
Computation Computation
Cluster | Time Saved | Cluster | Time Saved
(hours/day) (hours/day)
Cl 3898 C14 753
C2 2276 C15 755
C3 977 C16 2259
C4 1345 C17 3385
C5 7143 C18 528
C6 62 C19 4
C7 57 C20 415
C8 590 C21 606
C9 763 C22 2002
C10 2457 C23 1316
Cll 1924 C24 291
C12 368 C25 58
C13 105
Table 1: Minimum Computation Time Savings
There are anecdotes of system administrators manu-
ally running a common sub-expression on the daily input
and explicitly notifying programmers to avoid each pro-
gram performing the computation on its own and tying
up cluster resources. Nectar automatically supports in-
cremental computation and programmers do not need to
code them explicitly. As discussed in Section 2, Nectar
tries to produce the best possible query plan using the
cached results, significantly reducing computation time,
at the same time making it opaque to the user.
An unanticipated benefit of Nectar reported by our
users on the research cluster was that it aids in debugging
during program development. Programmers incremen-
tally test and debug pieces of their code. With Nectar the
debugging time significantly improved due to cache hits.
We quantify the effect of this on the production clusters.
We assumed that a program is a debugged version of an-
other program if they had almost the same queries ac-
cessing the same source data and writing the same de-
rived data, submitted by the same user and had the same
program name.
Table 2 shows the amount of debugging time that can
be saved by Nectar in the 90 day period. We present
results for the first 12 clusters due to space constraints.
Again, these are conservative estimates but shows sub-
stantial savings. For instance, in Cluster C1, a minimum
of 3 hours of debugging time can be saved per day. No-
tice that this is actual elapsed time, i.e., each day 3 hours
of computation on the cluster spent on debugging pro-
grams can be avoided with Nectar.
USENIX Association
USENIX Association
Debugging Debugging
Cluster | Time Saved | Cluster | Time Saved
(hours) (hours)
Cl 270 C7] 3
C2 211 C8 35
C3 24 C9 84
C4 101 C10 183
Or) 94 Cll 121
C6 8 C12 49
Table 2: Actual elapsed time saved on debugging in 90
days.
Managing Storage
Today, in datacenters, storage is manually managed.!
We studied storage statistics in our 240-node research
cluster that has been used by a significant number of
users over the last 2 to 3 years. We crawled this clus-
ter for derived objects and noted their last access times.
Of the 109 TB of derived data, we discovered that about
50% (54.5 TB) was never accessed in the last 250 days.
This shows that users often create derived datasets and
after a point, forget about them, leaving them occupying
unnecessary storage space.
We analyzed the production logs for the amount of de-
rived datasets written. When calculating the storage oc-
cupied by these datasets, we assumed that if a new job
writes to the same dataset as an old job, the dataset is
overwritten. Figure 7 shows the growth of derived data
storage in cluster Cl. It show an approximately linear
growth with the total storage occupied by datasets cre-
ated in 90 days being 670 TB.
Storage occupied by derived datasets
Figure 7: Growth of storage occupied by derived datasets
in Cluster Cl
'Nectar’s motivation in automatically managing storage partly
stems from the fact that we used to get periodic e-mail messages from
the administrators of the production clusters requesting us to delete our
derived objects to ease storage pressure in the cluster.
Cluster | Projected unreferenced
derived data (in TB)
Cl 2712
C5 368
C8 863
C13 995
C15 210
Table 3: Projected unreferenced data in 5 production
clusters
Assuming similar trends in data access time in our lo-
cal cluster and on the production clusters, Table 3 shows
the projected space occupied by unreferenced derived
datasets in 5 production clusters that showed a growth
similar to cluster C1. Any object that has not been refer-
enced in 250 days is deemed unreferenced. This result is
obtained by extrapolating the amount of data written by
jobs in 90 days to 2 years based on the storage growth
curve and predicting that 50% of that storage will not be
accessed in the last 250 days (based on the result from
our local cluster). As we see, production clusters cre-
ate a large amount of derived data, which if not properly
managed can create significant storage pressure.
4.2 System Deployment Experience
Each machine in our 240-node research cluster has two
dual-core 2.6GHz AMD Opteron 2218 HE CPUs, 16GB
RAM, four 750GB SATA drives, and runs Windows Ser-
ver 2003 operating system. We evaluate the comparative
performance of several programs with Nectar turned on
and off.
We use three datasets to evaluate the performance of
Nectar:
WordDoc Dataset. The first dataset is a collection of
Web documents. Each document contains a URL and its
content (as a list of words). The data size is 987.4 GB
. The dataset is randomly partitioned into 236 partitions.
Each partition has two replicas in the distributed file sys-
tem, evenly distributed on 240 machines.
ClickLog Dataset. The second dataset is a small sam-
ple from an anonymized click log of a commercial search
engine collected over five consecutive days. The dataset
is 160GB in size, randomly partitioned into 800 parti-
tions, two replicas each, evenly distributed on 240 ma-
chines.
SkyServer Dataset. This database is taken from the
Sloan Digital Sky Survey database [11]. It contains two
data files: 11.8 and 41.8 GBytes of data. Both files were
manually range-partitioned into 40 partitions using the
same keys.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) 85
86
Sub-computation Evaluation
We have four programs: WordAnalysis, TopWord,
MostDoc, and TopRatio that analyze the WordDoc
dataset.
WordAnalysis parses the dataset to generate the num-
ber of occurrences of each word and the number of doc-
uments that it appears in. TopWord looks for the top ten
most commonly used words in all documents. MostDoc
looks for the top ten words appearing in the largest num-
ber of documents. TopRatio finds the percentage of oc-
currences of the top ten mostly used words among all
words. All programs take the entire 987.4 GB dataset as
input.
Program Name Cumulative Time Saving
Nectar on | Nectar off
TopWord 16.1m 21h44m | 98.8%
MostDoc 17.5m 21h46m | 98.6%
TopRatio 21.2m 43h30m | 99.2%
Table 4: Saving by sharing a common sub-computation:
Document analysis
With Nectar on, we can cache the results of executing
the first program, which spends a huge amount of com-
putation analyzing the list of documents to output an ag-
gregated result of much smaller size (12.7 GB). The sub-
sequent three programs share a sub-computation with the
first program, which is satisfied from the cache. Table 4
shows the cumulative CPU time saved for the three pro-
grams. This behavior is not isolated, one of the programs
that uses the ClickLog dataset shows a similar pattern; we
do not report the results here for reasons of space.
Incremental Computation
We describe the performance of a program that stud-
ies query relevance by processing the ClickLog dataset.
When users search a phrase at a search engine, they click
the most relevant URLs returned in the search results.
Monitoring the URLs that are clicked the most for each
search phrase is important to understand query relevance.
The input to the query relevance program is the set of all
click logs collected so far, which increases each day, be-
cause a new log is appended daily to the dataset. This
program is an example where the initial dataset is large,
but the incremental updates are small.
Table 5 shows the cumulative CPU time with Nectar
on and off, the size of datasets and incremental updates
each day. We see that the total size of input data increases
each day, while the computation resource used daily in-
creases much slower when Nectar is on. We observed
similar performance results for another program that cal-
culates the number of active users, who are those that
clicked at least one search result in the past three days.
These results are not reported here for reasons of space.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
Data Size(GB) Time (m) Sane
Total | Update | On Off
Day3 | 68.20 40.50 93.0 | 107.5 | 13.49%
Day4 | 111.25 | 43.05 | 112.9 | 194.0 | 41.80%
Day5 | 152.19 | 40.94 | 164.6 | 325.8 | 49.66%
Table 5: Cumulative machine time savings for incremen-
tal computation.
Debugging Experience: Sky Server
Here we demonstrate how Nectar saves program de-
velopment time by shortening the debugging cycle. We
select the most time-consuming query (Q18) from the
Sloan Digital Sky Survey database [11]. The query iden-
tifies a gravitational lens effect by comparing the loca-
tions and colors of stars in a large astronomical table,
using a three-way Join over two input tables contain-
ing 11.8 GBytes and 41.8 GBytes of data, respectively.
The query is composed of four steps, each of which is
debugged separately. When debugging the query, the
first step failed and the programmer modified the code.
Within a couple of tries, the first step succeeded, and ex-
ecution continued to the second step, which failed, and
so on.
Table 6 shows the average savings in cumulative time
as each step is successively debugged with Nectar. To-
wards the end of the program, Nectar saves as much 88%
of the time.
Cumulative Time Saving
Nectar on | Nectar off
Step 1 47.4m 47.4m 0%
Steps 1-2 26.5m 62.5m 58%
Steps 1-3 35.5m 122.7m 11%
Steps 14 15.0m 129.3m 88%
Table 6: Debugging: SkyServer cumulative time
5 Related Work
Our overall system architecture is inspired by the Vesta
system [15]. Many high-level concepts and techniques
(e.g., the notion of primary and derived data) are directly
taken from Vesta. However, because of the difference in
application domains, the actual design and implementa-
tion of the main system components such as caching and
program rewriting are radically different.
Many aspects of query rewriting and caching in our
work are closely related to incremental view mainte-
nance and materialized views in the database litera-
ture [2, 5, 13, 19]. However, there are some important
differences as discussed in Section 3.1. Also, we are not
aware of the implementation of these ideas in systems
USENIX Association
USENIX Association
at the scale we describe in this paper. Incremental view
maintenance is concerned with the problem of updating
the materialized views incrementally (and consistently)
when data base tables are subjected to random updates.
Nectar is simpler in that we only consider append-only
updates. On the other hand, Nectar is more challenging
because we must deal with user-defined functions written
in a general-purpose programming language. Many of
the sophisticated view reuses given in [13] require anal-
ysis of the SQL expressions that is difficult to do in the
presence of user-defined functions, which are common
in our environment.
With the wide adoption of distributed execution
platforms like Dryad/DryadLINQ, MapReduce/Sawzall,
Hadoop/Pig [18, 29, 7, 25, 12, 24], recent work has in-
vestigated job patterns and resource utilization in data
centers [1, 14, 22, 23, 26]. These investigation of real
work loads have revealed a vast amount of wastage in
datacenters due to redundant computations, which is
consistent with our findings from logs of a number of
production clusters.
DryadInc [26] represented our early attempt to elim-
inate redundant computations via caching, even before
we started on the DryadLINQ project. The caching ap-
proach is quite similar to Nectar. However, it works at
the level of Dryad dataflow graph, which is too general
and too low-level for the system we wanted to build.
The two systems that are most related to Nectar are the
stateful bulk processing system described by Logothetis
et al. [22] and Comet [14]. These systems mainly fo-
cus on addressing the important problem of incremental
computation, which is also one of the problems Nectar
is designed to address. However, Nectar is a much more
ambitious system, attempting to provide a comprehen-
sive solution to the problem of automatic management
of data and computation in a datacenter.
As a design principle, Nectar is designed to be trans-
parent to the users. The stateful bulk processing sys-
tem takes a different approach by introducing new prim-
itives and hence makes state explicit in the programming
model. It would be interesting to understand the trade-
offs in terms of performance and ease of programming.
Comet, also built on top of Dryad and DryadLINQ,
also attempted to address the sub-computation problem
by co-scheduling multiple programs with common sub-
computations to execute together. There are two inter-
esting issues raised by the paper. First, when multiple
programs are involved in caching, it is difficult to de-
termine if two code segments from different programs
are identical. This is particularly hard in the presence
of user-defined functions, which is very common in the
kind of DryadLINQ programs targeted by both Comet
and Nectar. It is unclear how this determination is made
in Comet. Nectar addresses this problem by building a
sophisticated static program analyzer that allows us to
compute the dependency of user-defined code. Second,
co-scheduling in Comet requires submissions of multi-
ple programs with the same timestamp. It is therefore
not useful in all scenarios. Nectar instead shares sub-
computations across multiple jobs executed at different
times by using a datacenter-wide, persistent cache ser-
vice.
Caching function calls in a functional programming
language is well studied in the literature [15, 21, 27].
Memoization avoids re-computing the same function
calls by caching the result of past invocations. Caching
in Nectar can be viewed as function caching in the con-
text of large-scale distributed computing.
6 Discussion and Conclusions
In this paper, we described Nectar, a system that auto-
mates the management of data and computation in dat-
acenters. The system has been deployed on a 240-node
research cluster, and has been in use by a small number
of developers. Feedback has been quite positive. One
very popular comment from our users is that the system
makes program debugging much more interactive and
fun. Most of us, the Nectar developers, use Nectar to
develop Nectar on a daily basis, and found a big increase
in our productivity.
To validate the effectiveness of Nectar, we performed
a systematic analysis of computation logs from 25 pro-
duction clusters. As reported in Section 4, we have seen
huge potential value in using Nectar to manage the com-
putation and data in a large datacenter. Our next step is
to work on transferring Nectar to Microsoft production
datacenters.
Nectar is a complex distributed systems with multi-
ple interacting policies. Devising the right policies and
fine-tuning their parameters to find the right trade-offs is
essential to make the system work in practice. Our eval-
uation of these tradeoffs has been limited, but we are ac-
tively working on this topic. We hope we will continue to
learn a great deal with the ongoing deployment of Nectar
on our 240-node research cluster.
One aspect of Nectar that we have not explored is that
it maintains the provenance of all the derived datasets
in the datacenter. Many important questions about data
provenance could be answered by querying the Nectar
cache service. We plan to investigate this further in future
work.
What Nectar essentially does is to unify computation
and data, treating them interchangeably by maintaining
the dependency between them. This allows us to greatly
improve the datacenter management and resource utiliza-
tion. We believe that it represents a significant step for-
ward in automating datacenter computing.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) 87
88
Acknowledgments
We would like to thank Dennis Fetterly and Maya Hari-
dasan for their help with TidyFS. We would also like
to thank Martin Abadi, Surajit Chaudhuri, Yanlei Diao,
Michael Isard, Frank McSherry, Vivek Narasayya, Doug
Terry, and Fang Yu for many helpful comments. Thanks
also to the OSDI review committee and our shepherd Pei
Cao for their very useful feedback.
References
[1]
[2]
[3]
[4]
[5]
[6]
[7]
[8]
[9]
[10]
(11)
[12]
[13]
AGRAWAL, P., KIFER, D., AND OLSTON, C. Scheduling shared
scans of large data files. Proc. VLDB Endow. 1, | (2008), 958—
969.
AGRAWAL, S., CHAUDHURI, S., AND NARASAYYA, V. R.
Automated selection of materialized views and indexes in SQL
databases. In VLDB (2000), pp. 496-505.
ALVARO, P., CONDIE, T., CONWAY, N., ELMELEEGY, K.,
HELLERSTEIN, J. M., AND SEARS, R. Boom analytics: ex-
ploring data-centric, declarative programming for the cloud. In
EuroSys ’10: Proceedings of the 5th European conference on
Computer systems (2010), pp. 223-236.
BRODER, A. Z. Some applications of Rabins fingerprinting
method. In Sequences II: Methods in Communications, Security,
and Computer Science (1993), Springer-Verlag, pp. 143-152.
CERI, S., AND WIDOM, J. Deriving production rules for in-
cremental view maintenance. In VLDB ’91: Proceedings of the
17th International Conference on Very Large Data Bases (1991),
pp. 577-589.
CHAIKEN, R., JENKINS, B., LARSON, P.-A., RAMSEY, B.,
SHAKIB, D., WEAVER, S., AND ZHOU, J. SCOPE: easy and
efficient parallel processing of massive data sets. Proc. VLDB
Endow. 1, 2 (2008), 1265-1276.
DEAN, J., AND GHEMAWAT, S. Mapreduce: simplified data
processing on large clusters. Commun. ACM 51, | (2008), 107—
113.
FETTERLY, D., HARIDASAN, M., ISARD, M., AND SUN-
DARARAMAN, S. TidyFS: A simple and small distributed filesys-
tem. Tech. Rep. MSR-TR-2010-124, Microsoft Research, Octo-
ber 2010.
GOLUMBIC, M. C. Algorithmic Graph Theory and Perfect
Graphs (Annals of Discrete Mathematics, Vol. 57). North-
Holland Publishing Co., Amsterdam, The Netherlands, The
Netherlands, 2004.
Gray, J., CHAUDHURI, S., BOSWORTH, A., LAYMAN, A.,
REICHART, D., VENKATRAO, M., PELLOW, F., AND PIRA-
HESH, H. Data cube: A relational aggregation operator gen-
eralizing group-by, cross-tab, and sub-totals. Data Mining and
Knowledge Discovery 1, | (1997).
GRAY, J., SZALAY, A., THAKAR, A., KUNSZT, P.,
STOUGHTON, C., SLUTZ, D., AND VANDENBERG, J. Data min-
ing the SDSS SkyServer database. In Distributed Data and Struc-
tures 4: Records of the 4th International Meeting (Paris, France,
March 2002), Carleton Scientific, pp. 189-210. Also available as
MSR-TR-2002-01.
The Hadoop project.
http://hadoop.apache.org/.
HALEvy, A. Y. Answering Queries Using Views: A Survey.
VLDB J. 10, 4 (2001), 270-294.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
[14]
[15]
[16]
[17]
[18]
[19]
[20
[21
[22
[23
[24
[25]
[26]
[27]
[28]
[29]
HE, B., YANG, M., Guo, Z., CHEN, R., SU, B., LIN, W., AND
ZHOU, L. Comet: batched stream processing for data intensive
distributed computing. In ACM Symposium on Cloud Computing
(SOCC) (2010), pp. 63-74.
HEYDON, A., LEVIN, R., MANN, T., AND YU, Y. Software
Configuration Management Using Vesta. Springer-Verlag, 2006.
HEYDON, A., LEVIN, R., AND YU, Y. Caching function calls
using precise dependencies. In PLDI ’00: Proceedings of the
ACM SIGPLAN 2000 conference on Programming language de-
sign and implementation (New York, NY, USA, 2000), ACM,
pp. 311-320.
The HIVE project.
http: //hadoop.apache.org/hive/.
ISARD, M., BUDIU, M., YU, Y., BIRRELL, A., AND FET-
TERLY, D. Dryad: distributed data-parallel programs from se-
quential building blocks. In EuroSys ’07: Proceedings of the 2nd
ACM SIGOPS/EuroSys European Conference on Computer Sys-
tems 2007 (2007), pp. 59-72.
LEE, K. Y., SON, J. H., AND KIM, M. H. Efficient incremental
view maintenance in data warehouses. In CIKM ’01: Proceedings
of the tenth international conference on Information and knowl-
edge management (2001), pp. 349-356.
The LINQ project.
http://msdn.microsoft.com/netframework/
future/lind/.
Liu, Y. A., STOLLER, S. D., AND TEITELBAUM, T. Static
caching for incremental computation. ACM Trans. Program.
Lang. Syst. 20, 3 (1998), 546-585.
LOGOTHETIS, D., OLSTON, C., REED, B., WEBB, K., AND
YOcUM, K. Stateful bulk processing for incremental algorithms.
In ACM Symposium on Cloud Computing (SOCC) (2010).
OLSTON, C., REED, B., SILBERSTEIN, A., AND SRIVASTAVA,
U. Automatic optimization of parallel dataflow programs. In
ATC’08: USENIX 2008 Annual Technical Conference on Annual
Technical Conference (2008), pp. 267-273.
OLSTON, C., REED, B., SRIVASTAVA, U., KUMAR, R., AND
TOMKINS, A. Pig latin: a not-so-foreign language for data pro-
cessing. In SIGMOD ’08: Proceedings of the 2008 ACM SIG-
MOD international conference on Management of data (2008),
pp. 1099-1110.
PIKE, R., DORWARD, S., GRIESEMER, R., AND QUINLAN, S.
Interpreting the data: Parallel analysis with Sawzall. Scientific
Programming 13, 4 (2005).
Popa, L., BUDIU, M., YU, Y., AND ISARD, M. DryadInc:
Reusing work in large-scale computations. In Workshop on Hot
Topics in Cloud Computing (HotCloud) (San Diego, CA, June 15
2009).
PUGH, W., AND TEITELBAUM, T. Incremental computation
via function caching. In POPL ’89: Proceedings of the 16th
ACM SIGPLAN-SIGACT symposium on Principles of program-
ming languages (1989), pp. 315-328.
YU, Y., GUNDA, P. K., AND ISARD, M. Distributed aggregation
for data-parallel computing: interfaces and implementations. In
SOSP ’09: Proceedings of the ACM SIGOPS 22nd symposium on
Operating systems principles (2009), pp. 247-260.
Yu, Y., ISARD, M., FETTERLY, D., BUDIU, M., ERLINGSSON,
U., GuNDA, P. K., AND CuRREY, J. DryadLINQ: A system
for general-purpose distributed data-parallel computing using a
high-level language. In Proceedings of the 8th Symposium on
Operating Systems Design and Implementation (OSDI) (2008),
pp. 1-14.
USENIX Association
USENIX Association
Intrusion Recovery Using Selective Re-execution
Taesoo Kim, Xi Wang, Nickolai Zeldovich, and M. Frans Kaashoek
MIT CSAIL
ABSTRACT
RETRO repairs a desktop or server after an adversary com-
promises it, by undoing the adversary’s changes while
preserving legitimate user actions, with minimal user in-
volvement. During normal operation, RETRO records
an action history graph, which is a detailed dependency
graph describing the system’s execution. RETRO uses re-
jinement to describe graph objects and actions at multiple
levels of abstraction, which allows for precise dependen-
cies. During repair, RETRO uses the action history graph
to undo an unwanted action and its indirect effects by
first rolling back its direct effects, and then re-executing
legitimate actions that were influenced by that change.
To minimize user involvement and re-execution, RETRO
uses predicates to selectively re-execute only actions that
were semantically affected by the adversary’s changes,
and uses compensating actions to handle external effects.
An evaluation of a prototype of RETRO for Linux with
2 real-world attacks, 2 synthesized challenge attacks, and
6 attacks from previous work, shows that RETRO can
often repair the system without user involvement, and
avoids false positives and negatives from previous so-
lutions. These benefits come at the cost of 35-127% in
execution time overhead and of 4—150 GB of log space per
day, depending on the workload. For example, a HotCRP
paper submission web site incurs 35% slowdown and gen-
erates 4 GB of logs per day under the workload from 30
minutes prior to the SOSP 2007 deadline.
1 INTRODUCTION
Despite our best efforts to build secure computer systems,
intrusions are nearly unavoidable in practice. When faced
with an intrusion, a user is typically forced to reinstall
their system from scratch, and to manually recover any
documents and settings they might have had. Even if the
user diligently makes a complete backup of their system
every day, recovering from the attack requires rolling back
to the most recent backup before the attack, thereby losing
any changes made since then. Since many adversaries go
to great lengths to prevent the compromise from being
discovered, it can take days or weeks for a user to discover
that their machine has been broken into, resulting in a loss
of all user work from that period of time.
This paper presents RETRO, a system for retroactively
undoing past attacks and their indirect effects on a single
machine. With RETRO, an administrator specifies offend-
ing actions from the past, such as a TCP connection or
an HTTP request from an adversary, that they want to
undo. RETRO then repairs the system’s state (the file sys-
tem) by selectively undoing the offending actions—that
is, constructing a new system state, as if the offending
actions never took place, but all legitimate actions re-
mained. Thus, by selectively undoing the adversary’s
changes while preserving user data, RETRO makes intru-
sion recovery more practical.
To illustrate the challenges facing RETRO, consider the
following attack, which we will use as a running example
in this paper. Eve, an evil adversary, compromises a Linux
machine, and obtains a root shell. To mask her trail, she
removes the last hour’s entries from the system log. She
then creates several backdoors into the system, including
a new account for eve, and a PHP script that allows her to
execute arbitrary commands via HTTP. Eve then uses one
of these backdoors to download and install a botnet client.
To ensure continued control of the machine, Eve adds a
line to the /usr/bin/texi2pdf shell script (a wrapper
for I4TgX) to restart her bot. In the meantime, legitimate
users log in, invoke their own PHP scripts, use texi2pdf,
and root adds new legitimate users.
To undo attacks, RETRO provides a system-wide ar-
chitecture for recording actions, causes, and effects in
order to identify all the downstream effects of a compro-
mise. The key challenge is that a compromise in the past
may have effects on subsequent legitimate actions, espe-
cially if the administrator discovers an attack long after it
occurred. RETRO must sort out this entanglement auto-
matically and efficiently. In our running example, Eve’s
changes to the password file and to texi2pdf are entan-
gled with legitimate actions that modified or accessed the
password file, or used texi2pdf. If legitimate users ran
texi2pdf, their output depended on Eve’s actions, and
so did any programs that used that output in turn.
As described in §2, most previous systems require user
input to disentangle such actions. Typical previous solu-
tions are good at detecting a compromise and allow a user
to roll the system back to a check point before the com-
promise, but then ask the user to incorporate legitimate
changes from after the compromise manually; this can
be quite onerous if the attack has happened a long time
ago. Some solutions reduce the amount of manual work
for special cases (e.g., known viruses). The most recent
general solution for reducing user assistance (Taser [17])
incurs many false positives (undoing legitimate actions),
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) 89
90
or, after white-listing some actions to minimize false posi-
tives, it incurs false negatives (missing parts of the attack).
How can RETRO disentangle unwanted actions from le-
gitimate operations, and undo all effects of the adversary’s
actions that happened in the past, while preserving every
legitimate action? RETRO addresses these challenges with
four ideas:
First, RETRO models the entire system using a new
form of a dependency graph, which we call an action his-
tory graph. Like any dependency graph, the action history
graph represents objects in the system (such as files and
processes), and the dependencies between those objects
(corresponding to actions such as a process reading a file).
To record precise dependencies, the action history graph
supports refinement, that is, representing the same object
or action at multiple levels of abstraction. For example,
a directory inode can be refined to expose individual file
names in that directory, and a process can be refined into
function calls and system calls. The action history graph
also captures the semantics of each dependency (e.g., the
arguments and return values of an action).
Second, RETRO re-executes actions in the graph, such
as system calls or process invocations, that were influ-
enced by the offending changes. For example, undoing
undesirable actions may indirectly change the inputs of
later actions, and thus these actions must be re-executed
with their repaired inputs.
Third, RETRO uses predicates to do selective re-
execution of just the actions whose dependencies are
semantically different after repair, thereby minimizing
cascading re-execution. For example, if Eve modified
some file, and that file was later read by process P, we
may be able to avoid re-executing P if the part of the file
accessed by P is the same before and after repair.
Finally, to selectively re-execute existing applications,
RETRO uses shepherded re-execution to monitor the re-
execution of processes (§5.2.3), and stops re-execution
when the process state converges to the original execution
(such as when a process issues an identical exec call).
Using a prototype of RETRO for Linux, we show that
RETRO can recover from both real-world and synthetic
attacks, including our running example, while preserving
legitimate user changes. Out of ten experiment scenarios,
six required no user input to repair, two required user
confirmation that a conflicting login session belonged to
the attacker, and two required the user to manually redo
affected operations. We also show that RETRO’s ideas of
refinement, shepherded re-execution, and predicates are
key to repairing precisely the files affected by the attack,
and to minimizing user involvement. A performance eval-
uation shows that, for extreme workloads that issue many
system calls (such as continuously recompiling the Linux
kernel), RETRO imposes a 89—127% runtime overhead
and requires 100-150 GB of log space per day. For a
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
more realistic application, such as a HotCRP [23] confer-
ence submission site, these costs are 35% and 4 GB per
day, respectively. RETRO’s runtime cost can be reduced
by using additional cores, amounting to 0% for HotCRP
when one core is dedicated to RETRO.
The rest of the paper is organized as follows. The next
section compares RETRO with related work. §3 presents
an overview of RETRO’s architecture and workflow. 84
discusses RETRO’s action history graph in detail, and
§5 describes RETRO’s repair managers. Our prototype
implementation is described in 86, and §7 evaluates the
effectiveness and performance of RETRO. Finally, §8 dis-
cusses the limitations and future work, and §9 concludes.
2 RELATED WORK
This section relates RETRO to industrial and academic
solutions for recovery after a compromise, and prior tech-
niques that RETRO builds on.
2.1 Repair solutions
One line of industrial solutions is anti-virus tools, which
can revert changes made by common malware, such as
Windows registry keys and files comprising a known virus.
For example, tools such as [34] can generate remediation
procedures for a given piece of malware. While such
techniques work for known malware that behaves in pre-
dictable ways, they incur both false positives and false
negatives, especially for new or unpredictable malware,
and may not be able to recover from attacks where some
information is lost, such as file deletions or overwrites.
They also cannot repair changes that were a side-effect of
the attack, such as changes made by a trojaned program,
or changes made by an interactive adversary, whereas
RETRO can undo such changes.
Another line of industrial solutions is systems that help
users roll back unwanted changes to system state. These
solutions include Windows System Restore [18], Win-
dows Driver Rollback [30], Time Machine [4], and numer-
ous backup tools. These tools perform coarse-grained re-
covery, and require the user to identify what files were af-
fected. RETRO uses the action history graph to track down
all effects of an attack, repairs precisely those changes,
and repairs all side-effects of the attack, without requiring
the user to guess what files were affected.
A final line of popular solutions is using virtual ma-
chines as a form of whole-system backup. Using Re-
Virt [14] or Moka5 [11, 31], an administrator can roll
back to a checkpoint before an attack, losing both the
attacker’s changes and any legitimate changes since that
point. One could imagine a system that replays recorded
legitimate network packets to the virtual machine to re-
apply legitimate changes. However, if there are even
subtle dependencies between omitted and replayed pack-
ets, the replayed packets will result in conflicts or external
USENIX Association
USENIX Association
Compensating
actions
Repair
controller
Repair managers
Figure 1: Overview of RETRO’s architecture, including major components and their interactions. Shading indicates components introduced by
RETRO. Striped shading of checkpoints indicates that RETRO reuses existing file system snapshots when available.
dependencies, requiring user input to proceed. By record-
ing dependencies and re-executing actions at many levels
of abstraction using refinement, RETRO avoids such con-
flicts and can preserve legitimate changes without user
input.
Academic research has tried to improve over the in-
dustrial solutions by attempting to make solutions more
automatic. Brown’s undoable email store [10] shows how
an email server can recover from operator mistakes, by
turning all operations into verbs, such as SMTP or IMAP
commands. Unlike RETRO, Brown’s approach is limited
to recovering from accidental operator mistakes. As a
result, it cannot deal with an adversary that goes outside
of the verb model and takes advantage of a vulnerability
in the IMAP server software, or guesses root’s password
to log in via ssh. Moreover, it cannot recover from actions
that had system-wide effects spanning multiple applica-
tions, files, and processes.
The closest related work to RETRO is Taser [17], which
uses taint tracking to find files affected by a past attack.
Taser suffers from false positives, erroneously rolling back
hundreds or thousands of files. To prevent false positives,
Taser uses a white-list to ignore taint for some nodes or
edges. This causes false negatives, so an attacker can
bypass Taser altogether. While extensions of Taser catch
some classes of attacks missed due to false negatives [40],
RETRO has no need for white-listing. RETRO recovers
from all attacks presented in the Taser paper with no
false positives or false negatives. RETRO avoids Taser’s
limitations by using a design based on the action history
graph, and techniques such as predicates and re-execution,
as opposed to Taser’s taint propagation.
Polygraph [29] uses taint tracking to recover from com-
promised devices in a data replication system, and incurs
false positives like Taser. Unlike RETRO, Polygraph can
recover from compromises in a distributed system.
2.2 Related techniques
The use of dependency information for security has been
widely explored in many contexts, including informa-
tion flow control [25, 45], taint tracking [44], data prove-
nance [9], forensics [21], system integrity [8], and so
on. A key difference in RETRO’s action history graph
is the use of exact dependency data to decide whether a
dependency has semantically changed at repair time.
RETRO assumes that intrusion detection and analysis
tools, such as [7, 12, 14, 15, 19-22, 24, 40, 43], detect
attacks and pinpoint attack edges. RETRO’s intrusion de-
tection is based on BackTracker [21]. A difference is that
RETRO’s action history graph records more information
than BackTracker, which RETRO needs for repair (but
doesn’t use yet for detection).
Transactions [33, 36] help revert unwanted changes
before commit, whereas RETRO can selectively undo
“committed” actions. Database systems use compensating
transactions to revert committed transactions, including
malicious transactions [3, 27]; RETRO similarly uses com-
pensating actions to deal with externally-visible changes.
3 OVERVIEW
RETRO consists of several components, as shown in Fig-
ure 1. During normal execution, RETRO’s kernel module
records a log of system execution, and creates periodic
checkpoints of file system state. When the system ad-
ministrator notices a problem, he or she uses RETRO to
track down the initial intrusion point. Given an intrusion
point, RETRO reverts the intrusion, and repairs the rest
of the system state, relying on the system administrator
to resolve any conflicts (e.g., both the adversary and a
legitimate user modified the same line of the password
file). The rest of this section describes these phases of
operation in more detail, and outlines the assumptions
made by RETRO about the system and the adversary.
Normal execution. As the computer executes, RETRO
must record sufficient information to be able to revert
the effects of an attack. To this end, RETRO records
periodic checkpoints of persistent state (the file system),
so that it can later roll back to a checkpoint. RETRO
does not require any specialized format for its file system
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) 91
92
checkpoints; if the file system already creates periodic
snapshots, such as [26, 32, 37, 38], RETRO can simply
use these snapshots, and requires no checkpointing of its
own. In addition to rollback, RETRO must be able to re-
execute affected computations. To this end, RETRO logs
actions executed over time, along with their dependencies.
The resulting checkpoints and actions comprise RETRO’s
action history graph, such as the one shown in Figure 2.
The action history graph consists of two kinds of ob-
jects: data objects, such as files, and actor objects, such
as processes. Each object has a set of checkpoints, rep-
resenting a copy of its state at different points in time.
Each actor object additionally consists of a set of actions,
representing the execution of that actor over some period
of time. Each action has dependencies from and to other
objects in the graph, representing the objects accessed
and modified by that action. Actions and checkpoints of
adjacent objects are ordered with respect to each other, in
the order in which they occurred.!
RETRO stores the action history graph in a series of log
files over time. When RETRO needs more space for new
log files, it garbage-collects older log files (by deleting
them). Log files are only useful to RETRO in conjunction
with a checkpoint that precedes the log files, so log files
with no preceding checkpoint can be garbage-collected.
In practice, this means that RETRO keeps checkpoints
for at least as long as the log files. By design, RETRO
cannot recover from an intrusion whose log files have
been garbage collected; thus, the amount of log space
allocated to logs and checkpoints controls RETRO’s re-
covery “horizon”. For example, a web server running the
HotCRP paper review software [23] logs 4 GB of data per
day, so if the administrator dedicates a 2 TB disk ($100)
to RETRO, he or she can recover from attacks within the
past year, although these numbers strongly depend on the
application.
Intrusion detection. At some point after an adversary
compromises the system, the system administrator learns
of the intrusion, perhaps with the help of an intrusion
detection system. To repair from the intrusion, the system
administrator must first track down the initial intrusion
point, such as the adversary’s network connection, or
a user accidentally running a malware binary. RETRO
provides a tool similar to BackTracker [21] that helps
the administrator find the intrusion point, starting from
the observed symptoms, by leveraging RETRO’s action
history graph. In the rest of this paper, we assume that an
intrusion detection system exists, and we do not describe
our BackTracker-like tool in any more detail.
Repair. Once the administrator finds the intrusion point,
he or she reboots the system, to discard non-persistent
'For simplicity, our prototype globally orders all checkpoints and
actions for all objects.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
state, and invokes RETRO’s repair controller, specifying
the name of the intrusion point determined in the previous
step.? The repair controller undoes the offending action,
A, by rolling back objects modified by A to a previous
checkpoint, and replacing A with a no-op in the action
history graph. Then, using the action history graph, the
controller determines which other actions were poten-
tially influenced by A (e.g., the values of their arguments
changed), rolls back the objects they depend on (e.g.,
their arguments) to a previous checkpoint, re-executes
those actions in their corrected environment (e.g., with
the rolled-back arguments), and then repeats the process
for actions that the re-executed actions may have influ-
enced. This process will also undo subsequent actions
by the adversary, since the action that initially caused
them, A, has been reverted. Thus, after repair, the system
will contain the effects of all legitimate actions since the
compromise, but none of the effects of the attack.
To minimize re-execution and to avoid potential con-
flicts, the repair controller checks whether the inputs to
each action are semantically equivalent to the inputs dur-
ing original execution, and skips re-execution in that case.
In our running example, if Alice’s sshd process reads a
password file that Eve modified, it might not be necessary
to re-execute sshd if its execution only depended on AI-
ice’s password entry, and Eve did not change that entry. If
Alice’s sshd later changed her password entry, then this
change will not result in a conflict during repair because
the repair controller will determine that her change to the
password file could not have been influenced by Eve.
RETRO’s repair controller must manipulate many kinds
of objects (e.g., files, directories, processes, etc.) and
re-execute many types of actions (e.g., system calls and
function calls) during repair. To ensure that RETRO’s de-
sign is extensible, RETRO’s action history graph provides
a well-defined API between the repair controller and in-
dividual graph objects and actions. Using this API, the
repair controller implements a generic repair algorithm,
and interacts with the graph through individual repair
managers associated with each object and action in the
action history graph. Each repair manager, in turn, tracks
the state associated with their respective object or action,
implements object/action-specific operations during re-
pair, and efficiently stores and accesses the on-disk state,
logs, and checkpoints.
External dependencies. During repair, RETRO may
discover that changes made by the adversary were ex-
ternally visible. RETRO relies on compensating actions to
deal with external dependencies where possible. For ex-
ample, if a user’s terminal output changes, RETRO sends
Each object and action in the action history graph has a unique
name, as described in §5.
USENIX Association
USENIX Association
a diff between the old and new terminal sessions to the
user in question.
In some cases, RETRO does not have a compensat-
ing action to apply. If Eve, from our running example,
connected to her botnet client over the network, RETRO
would not be able to re-execute the connection during
repair (the connection will be refused since the botnet
will no longer be running). When such a situation arises,
RETRO’s repair controller pauses re-execution and asks
the administrator to manually re-execute the appropriate
action. In the case of Eve’s connection, the administra-
tor can safely do nothing and tell the repair controller to
resume.
Assumptions. RETRO makes three significant assump-
tions. First, RETRO assumes that the system administrator
detects intrusions in a timely manner, that is, before the
relevant logs are garbage-collected. An adversary that is
aware of RETRO could compromise the system and then
try to avoid detection, by minimizing any activity until
RETRO garbage-collects the logs from the initial intru-
sion. If the initial intrusion is not detected in time, the
administrator will not be able to revert it directly, but this
strategy would greatly slow down attackers. Moreover,
the administrator may be able to revert subsequent actions
by the adversary that leveraged the initial intrusion to
cause subsequent notable activity.
Second, RETRO assumes that the administrator
promptly detects any intrusions with wide-ranging effects
on the execution of the entire system. If such intrusions
persist for a long time, RETRO will require re-execution
of large parts of the system, potentially incurring many
conflicts and requiring significant user input. However,
we believe this assumption is often reasonable, since the
goal of many adversaries is to remain undetected for as
long as possible (e.g., to send more spam, or to build up a
large botnet), and making pervasive changes to the system
increases the risk of detection.
Third, for this paper, we assume that the adversary com-
promises a computer system through user-level services.
The adversary may install new programs, add backdoors
to existing programs, modify persistent state and con-
figuration files, and so on, but we assume the adversary
doesn’t tamper with the kernel, file system, checkpoints,
or logs. RETRO’s techniques rely on a detailed under-
standing of operating system objects, and our assumptions
allow RETRO to trust the kernel state of these objects. We
rely on existing techniques for hardening the kernel, such
as [16, 28, 39, 41], to achieve this goal in practice.
4 ACTION HISTORY GRAPH
RETRO’s design centers around the action history graph,
which represents the execution of the entire system over
Time
wee Legend:
| actor object
2 data object
I action
@ object checkpoint
>
>
dependency
example intrusion point
Figure 2: A simplified view of the action history graph depicting Eve’s
attack in our running example. In this graph, attacker Eve adds an
account for herself to /etc/passwd, after which root adds an account
for Alice, and Alice logs in via ssh. As an example, we consider Eve’s
write to the password file to be the attack action, although in reality,
the attack action would likely be the network connection that spawned
Eve’s process in the first place. Not shown are intermediate data objects,
and system call actors, described in §4.3 and Figure 4.
time. The action history graph must address four require-
ments in order to disentangle attacker actions from le-
gitimate operations. First, it must operate system-wide,
capturing all dependencies and actions, to ensure that
RETRO can detect and repair all effects of an intrusion.
Second, the graph must support fine-grained re-execution
of just the actions affected by the intrusion, without hav-
ing to re-execute unaffected actions. Third, the graph
must be able to disambiguate attack actions from legiti-
mate operations whenever possible, without introducing
false dependencies. Finally, recording and accessing the
action history graph must be efficient, to reduce both run-
time overheads and repair time. The rest of this section
describes the design of RETRO’s action history graph.
4.1 Repair using the action history graph
RETRO represents an attack as a set of attack actions. For
example, an attack action can be a process reading data
from the attacker’s TCP connection, a user inadvertently
running malware, or an offending file write. Given a set
of attack actions, RETRO repairs the system in two steps,
as follows.
First, RETRO replaces the attack actions with benign
actions in the action history graph. For example, if the
attack action was a process reading a malicious request
from the attacker’s TCP connection, RETRO removes the
request data, as if the attacker never sent any data on that
connection. If the attack action was a user accidentally
running malware, RETRO changes the user’s exec system
call to run /bin/true instead of the malware binary.
Finally, if the attack action was an unwanted write to a
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) 93
94
Function or variable Semantics
set(checkpt) object.checkpts
void object.rollback(c)
set(action) actor_object.actions
set(action) data_object.readers
set(action) data_object.writers
set(data_object) data_object.parts
Set of available checkpoints for this object.
Roll back this object to checkpoint c.
Set of actions that comprise this actor object.
Set of actions that have a dependency from this data object.
Set of actions that have a dependency to this data object.
Set of data objects whose state is part of this data object.
action.actor
action.inputs
action.outputs
actor _object
set(data_object)
set(data_object)
bool action.equiv()
bool action.connect()
void action.redo()
Actor containing this action.
Set of data objects that this action depends on.
Set of data objects that depend on this action.
Check whether any inputs of this action have changed.
Add dependencies for new inputs and outputs, based on new inputs.
Re-execute this action, updating output objects.
Figure 3: Object (top) and action (bottom) repair manager API.
file, as in Figure 2, RETRO replaces the action with a zero-
byte write. RETRO includes a handful of such benign
actions used to neutralize intrusion points found by the
administrator.
Second, RETRO repairs the system state to reflect the
above changes, by iteratively re-executing affected ac-
tions, starting with the benign replacements of the at-
tack actions themselves. Prior to re-executing an action,
RETRO must roll back all input and output objects of that
action, as well as the actor itself, to an earlier checkpoint.
For example, in Figure 2, RETRO rolls back the output of
the attack action—namely, the password file object—to
its earlier checkpoint.
RETRO then considers all actions with dependencies to
or from the objects in question, according to their time
order. Actions with dependencies fo the object in question
are re-executed, to reconstruct the object. For actions
with dependencies from the object in question, RETRO
checks whether their inputs are semantically equivalent
to their inputs during original execution. If the inputs
are different, such as the useradd command reading the
modified password file in Figure 2, the action will be
re-executed, following the same process as above. On
the other hand, if the inputs are semantically equivalent,
RETRO skips re-execution, avoiding the repair cascade.
For example, re-executing sshd may be unnecessary, if
the password file entry accessed by sshd is the same
before and after repair. We will describe shortly how
RETRO determines this (in $4.4 and Figure 5).
4.2 Graph API
As described above, repairing the system requires three
functions: rolling back objects to a checkpoint, re-
executing actions, and checking an action’s input depen-
dencies for semantic equivalence. To support different
types of objects and actions in a system-wide action his-
tory graph, RETRO delegates these tasks, as well as track-
ing the graph structure itself, to repair managers associ-
ated with each object and action in the graph.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
A manager consists of two halves: a runtime half, re-
sponsible for recording logs and checkpoints during nor-
mal execution, and a repair-time half, responsible for
repairing the system state once the system administrator
invokes RETRO to repair an intrusion. The runtime half
has no pre-defined API, and needs to only synchronize
its log and checkpoint format with the repair-time half.
On the other hand, the repair-time half has a well-defined
API, shown in Figure 3.
Object manager. During normal execution, object
managers are responsible for making periodic checkpoints
of objects. For example, the file system manager takes
snapshots of files, such as a copy of /etc/passwd in Fig-
ure 2. Process objects also have checkpoints in the graph,
although in our prototype, the only supported process
checkpoint is the initial state of a process immediately
prior to exec.
During repair, an object manager is responsible for
maintaining the state represented by its object. For per-
sistent objects, the manager uses the on-disk state, such
as the actual file for a file object. For ephemeral objects,
such as processes or pipes, the manager keeps a temporary
in-memory representation to help action managers redo
actions and check predicates, as we describe in §5.
An object manager provides one main procedure in-
voked during repair, o.rollback(v), which rolls back ob-
ject o’s state to checkpoint v. For a file object, this means
restoring the on-disk file from snapshot v. For a pro-
cess, this means constructing an initial, paused process in
preparation for redoing exec, as we will discuss in §5.2.3;
since there is only one kind of process checkpoint, v is
not used. If the object was last checkpointed long ago,
RETRO will need to re-execute all subsequent actions that
modified the data object, or that comprise the actor object.
Action manager. During normal execution, action man-
agers are responsible for recording all actions executed
by actors in the system. For each action, the manager
records enough information to re-execute the same action
at repair time, as well as to check whether the inputs are
USENIX Association
USENIX Association
semantically equivalent (e.g., by recording the data read
from a file).
At repair time, an action manager provides three proce-
dures. First, a.redo() re-executes action a, reading new
data from a’s input objects and modifying the state of
a’s output objects. For example, redoing a file write ac-
tion modifies the corresponding file in the file system; if
the action was not otherwise modified, this would write
the same data to the same offset as during original ex-
ecution. Second, a.equiv() checks whether a’s inputs
have semantically changed since the original execution.
For instance, equiv on a file read action checks whether
the file contains the same data at the same offset (and,
therefore, whether the read call would return the same
data). Finally, a.connect() updates action a’s input and
output dependencies, in case that changed inputs result in
the action reading or modifying new objects. To ensure
that past dependencies are not lost, connect only adds,
and never removes, dependencies (even if the action in
question does not use that dependency).
4.3 Refining actor objects:
Finer-grained re-execution
An important goal of RETRO’s design is minimizing re-
execution, so as to avoid the need for user input to handle
potential conflicts and external dependencies. It is of-
ten necessary to re-execute a subset of an actor’s actions,
but not necessarily the entire actor. For example, after
rolling back a file like /etc/passwd to a checkpoint that
was taken long ago, RETRO needs to replay all writes
to that file, but should not need to re-execute the pro-
cesses that issued those writes. Similarly, in Figure 2,
RETRO would ideally re-execute only a part of sshd that
checks whether Alice’s password entry is the same, and
if so, avoid re-executing the rest of sshd, which would
lead to an external dependency because cryptographic
keys would need to be re-negotiated. Unfortunately, re-
executing a process from an intermediate state is difficult
without process checkpointing.
To address this challenge, RETRO refines actors in the
action history graph to explicitly denote parts of a pro-
cess that can be independently re-executed. For example,
RETRO models every system call issued by a process by a
separate system call actor, comprising a single system call
action, as shown in Figure 4. The system call arguments,
and the result of the system call, are explicitly represented
by system call argument and return value objects. This
allows RETRO to re-execute individual system calls when
necessary (e.g., to re-construct a file during repair), while
avoiding re-execution of entire processes if the return
values of system calls remain the same.
The same technique is also applied to re-execute spe-
cific functions instead of an entire process. Figure 5 shows
a part of the action history graph for our running example,
S is
¥ oe
s » 2
3° Ry xD a
S oe :
S
x ¢
@
WS
¥
eel ae RS
ss
- - 7 A
Ss
----- >
J
oO
SV >
< e
Figure 4: An illustration of the system call actor object and arguments
and return value data objects, for Eve’s write to the password file from
Figure 2. Legend is the same as in Figure 2.
& >
rs Oe se
2 > s OSs 30”
3 RS cS & Se & Re oe
SO
‘ Ye 2 em x2 we Y
@
¥ $? -
‘. ve
oe ae
eS =
3
er, s
RS
Pp Pred
Se
SAS
SS
©
Figure 5: An illustration of refinement in an action history graph, de-
picting the use of additional actors to represent a re-executable call to
getpwnam from sshd. Legend is the same as in Figure 2.
in which sshd creates a separate actor to represent its call
to getpwnam("alice"). While getpwnam’s execution
depends on the entire password file, and thus must be
re-executed if the password file changes, its return value
contains only Alice’s password entry. If re-execution
of getpwnam produces the same result, the rest of sshd
need not be re-executed. §5 describes such higher-level
managers in more detail.
The same mechanism helps RETRO create benign re-
placements for attack actions. For example, in order
to undo a user accidentally executing malware, RETRO
changes the exec system call’s arguments to invoke
/bin/true instead of the malware binary. To do this,
RETRO synthesizes a new checkpoint for the object repre-
senting exec’s arguments, replacing the original malware
binary path with /bin/true, and rolls back that object to
the newly-created “checkpoint”, as illustrated in Figure 6
and §4.5.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) 95
4.4 Refining data objects:
Finer-grained data dependencies
While OS-level dependencies ensure completeness, they
can be too coarse-grained, leading to false dependencies,
such as every process depending on the /tmp directory.
RETRO’s design addresses this problem by refining the
same state at different levels of abstraction in the graph
when necessary. For instance, a directory manager creates
individual objects for each file name in a directory, and
helps disambiguate directory lookups and modifications
by recording dependencies on specific file names.
The challenge in supporting refinement in the action
history graph lies in dealing with multiple objects repre-
senting the same state. For example, the state of a single
directory entry is a part of both the directory manager’s
object for that specific file name, as well as the file man-
ager’s node for that directory’s inode. On one hand, we
would like to avoid creating dependencies to and from the
underlying directory inode, to prevent false dependencies.
On the other hand, if some process does directly read the
underlying directory inode’s contents, it should depend
on all of the directory entries in that directory.
To address this challenge, each object in RETRO keeps
track of other objects that represent parts of its state. For
example, the manager of each directory inode keeps track
of all the directory entry objects for that directory. The ob-
ject manager exposes this set of parts through the o.parts
property, as shown in Figure 3. In most cases, the man-
ager tracks its parts through hierarchical names, as we
discuss in §5.
RETRO’s OS manager records all dependencies, even
if the same dependency is also recorded by a higher-level
manager. This means that RETRO can determine trust
in higher-level dependencies at repair time. If the appro-
priate manager mediated all modifications to the larger
object (such as a directory inode), and the manager was
not compromised, RETRO can safely use finer-grained
objects (such as individual directory entry objects). Oth-
erwise, RETRO uses coarse-grained but safe OS-level
dependencies.
4.5 Repair controller
RETRO uses a repair controller to repair system state with
the help of object and action managers. Figure 6 sum-
marizes the pseudo-code for the repair controller. The
controller, starting from the REPAIR function, creates a
parallel “repaired” timeline by re-executing actions in the
order that they were originally executed. To do so, the
controller maintains a set of objects that it is currently
repairing (the nodes hash table), along with the last action
that it performed on that object. REPAIRLOOP continu-
ously attempts to re-execute the next action, until it has
considered all actions, at which point the system state is
fully repaired.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
function ROLLBACK(node, checkpt)
node.rollback(checkpt)
state[node] := checkpt
function PREPAREREDO(action)
if saction.connect() then return FALSE
if state[action.actor] > action then
cps := action.actor.checkpts
cp := max(c € cps|c < action)
ROLLBACK(action.actor, cp)
return FALSE
for all o € (action.inputs U action.outputs) do
if state[o] < action then continue
ROLLBACK(o0, max(c € o.checkpts|c < action))
return FALSE
return TRUE
function PICK ACTION()
actions := 0
for all o € state | ois actor object do
actions += min(a € o.actions|a > state[o])
for all o € state | ois data object do
actions += min(a € o.readers U
o.writers|a > state[o])
return min(actions)
function REPAIRLOOP()
while a := PICKACTION() do
if a.equiv() and state[o] > a,
Vo € a.outputs U a.actor then
for all € a.inputs M keys(state) do
stateli] =a
continue > skip semantically-equivalent action
if PREPAREREDO(a) then
a.redo()
for all o € a.inputs U a.outputs U a.actor do
state[o] := a
function REPAIR(repair_obj, repair_cp)
ROLLBACK(repair_obj, repair_cp)
REPAIRLOOP( )
Figure 6: The repair algorithm.
To choose the next action for re-execution, REPAIR-
Loop invokes PICKACTION, which chooses the earliest
action that hasn’t been re-executed yet, out of all the ob-
jects being repaired. If the action’s inputs are the same
(according to equiv), and none of the outputs of the ac-
tion need to be reconstructed, REPAIRLOOP does not
re-execute the action, and just advances the state of the
action’s input nodes. If the action needs to be re-executed,
REPAIRLOOP invokes PREPAREREDO, which ensures
that the action’s actor, input objects, and output objects
are all in the right state to re-execute the action (by rolling
back these objects when appropriate). Once PREPARE-
REDO indicates it is ready, REPAIRLOOP re-executes the
action and updates the state of the actor, input, and output
USENIX Association
USENIX Association
objects. Finally, REPAIR invokes REPAIRLOOP in the
first place, after rolling back repair_obj to the (newly-
synthesized) checkpoint repair _cp, as described in §4.3.
Not shown in the pseudo-code is handling of refined
objects. When the controller rolls back an object that has
a non-empty set of parts, it must consider re-executing
actions associated with those parts, in addition to actions
associated with the larger object. Also not shown is the
checking of integrity for higher-level dependencies, as
described in §4.4.
5 OBJECT AND ACTION MANAGERS
This section describes RETRO’s object and action man-
agers, starting with the file system and OS managers that
guarantee completeness of the graph, and followed by
higher-level managers that provide finer-grained depen-
dencies for application-specific parts of the graph.
5.1 File system manager
The file system manager is responsible for all file objects.
To uniquely identify files, the manager names file objects
by (device, part, inode). The device and part components
identify the disk and partition holding the file system.
Our current prototype disallows direct access to partition
block devices, so that file system dependencies are always
trusted. The inode number identifies a specific file by in-
ode, without regard to path name. To ensure that files can
be uniquely identified by inode number, the file system
manager prevents inode reuse until all checkpoints and
logs referring to the inode have been garbage-collected.
During normal operation, the file system manager must
periodically checkpoint its objects (including files and
directories), using any checkpointing strategy. Our im-
plementation relies on a snapshotting file system to make
periodic snapshots of the entire file system tree (e.g., once
per day). This works well for systems which already cre-
ate daily snapshots [26, 32, 37, 38], where the file system
manager can simply leverage existing snapshots. Upon
file deletion, the file system manager moves the deleted
inode into a special directory, so that it can reuse the same
exact inode number on rollback. The manager preserves
the inode’s data contents, so that RETRO can undo an
unlink operation by simply linking the inode back into a
directory (see §5.3).
During repair, the file system manager’s rollback
method uses a special kernel module to open the check-
pointed file as well as the current file by their inode num-
ber. Once the repair manager obtain a file descriptor for
both inodes, it overwrites the current file’s contents with
the checkpoint’s contents, or re-constructs an identical set
of directory entries, for directory inodes. On rollback to a
file system snapshot where the inode in question was not
allocated yet, the file system manager truncates the file to
zero bytes, as if it was freshly created. As a precaution,
the file system manager creates a new file system snapshot
before initiating any rollback.
5.2. OS manager
The OS manager is responsible for process and system
call actors, and their actions. The manager names each
process in the graph by (bootgen, pid, pidgen, execgen).
bootgen is a boot-up generation number to distinguish
process IDs across reboots. pid is the Unix process
ID, and pidgen is a generation number for the pro-
cess ID, used to distinguish recycled process IDs. Fi-
nally, execgen counts the number of times a process
called the exec system call; the OS manager logically
treats exec as creating a new process, albeit with the
same process ID. The manager names system calls by
(bootgen, pid, pidgen, execgen, sysid), where sysid is a
per-process unique ID for that system call invocation.
5.2.1 Recording normal execution
During normal execution, the OS manager intercepts
and records all system calls that create dependencies to
or from other objects (i.e., not getpid, etc), recording
enough information about the system calls to both re-
execute them at repair time, and to check whether the
inputs to the system call are semantically equivalent. The
OS manager creates nominal checkpoints of process and
system call actors. Since checkpointing of processes mid-
execution is difficult [13, 35], our OS manager check-
points actors only in their “initial” state immediately prior
to exec, denoted by |. The OS manager also keeps
track of objects representing ephemeral state, including
pipes and special devices such as /dev/null. Although
RETRO does not attempt to repair this state, having these
objects in the graph helps track and check dependen-
cies using equiv during repair, and to perform partial
re-execution.
5.2.2 Action history graph representation
In the action history graph, the OS manager represents
each system call by two actions in the process actor, two
intermediate data objects, and a system call actor and ac-
tion, as shown in Figure 4. The first process action, called
the syscall invocation action, represents the execution of
the process up until it invokes the system call. This action
conceptually places the system call arguments, and any
other relevant state, into the system call arguments object.
For example, the arguments for a file write include the
target inode, the offset, and the data. The arguments for
exec, on the other hand, include additional information
that allows re-executing the system call actor without hav-
ing to re-execute the process actor, such as the current
working directory, file descriptors not marked O_CLOEXEC,
and so on.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) 97
98
The system call action, in a separate actor, conceptually
reads the arguments from this object, performs the system
call (incurring dependencies to corresponding objects),
and writes the return value and any returned data into
the return value object. For example, a write system
call action, shown in Figure 4, creates a dependency to
the modified file, and stores the number of bytes written
into the return value object. Finally, the second process
action, called the syscall return action, reads the returned
data from that object, and resumes process execution. In
case of fork or exec, the OS manager creates two return
objects and two syscall return actions, representing return
values to both the old and new process actors. Thus, every
process actor starts with a syscall return action, with a
dependency from the return object for fork or exec.
In addition to system calls, Unix processes interact
with memory-mapped files. RETRO cannot re-execute
memory-mapped file accesses without re-executing the
process. Thus, the OS manager associates dependencies
to and from memory-mapped files with the process’s own
actions, as opposed to actions in a system call actor. In par-
ticular, every process action (either syscall invocation or
return) has a dependency from every file memory-mapped
by the process at that time, and a dependency to every file
memory-mapped as writable at that time.
5.2.3 Shepherded re-execution
During repair, the OS manager must re-execute two types
of actors: process actors and system call actors. For sys-
tem call actors, when the repair controller invokes redo,
the OS manager reads the (possibly changed) values from
the system call arguments object, executes the system call
in question, and places return data into the return object.
equiv on a system call action checks whether the input
objects have the same values as during the original ex-
ecution. Finally, connect reads the (possibly changed)
inputs, and creates any new dependencies that result. For
example, if a stat system call could not find the named
file during original execution, but RETRO restores the file
during repair, connect would create a new dependency
from the newly-restored file.
For process actors, the OS manager represents the
state of a process during repair with an actual process
being shepherded via the ptrace debug interface. On
p.rollback(L), the OS manager creates a fresh process
for process object p under ptrace. When the repair
controller invokes redo on a syscall return action, the
OS manager reads the return data from the correspond-
ing system call return object, updates the process state
using PTRACE_POKEDATA and PTRACE_SETREGS, and al-
lows the process to execute until it’s about to invoke the
next system call. equiv on a system call return action
checks if the data in the system call return object is the
same as during the original execution. When the repair
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
controller invokes redo on the subsequent syscall invo-
cation action, the OS manager simply marshals the argu-
ments for the system call invocation into the correspond-
ing system call arguments object. This allows the repair
controller to separately schedule the re-execution of the
system call, or to re-use previously recorded return data.
Finally, connect does nothing for process actions.
One challenge for the OS manager is to deal with pro-
cesses that issue different system calls during re-execution.
The challenge lies in matching up system calls recorded
during original execution with system calls actually is-
sued by the process during re-execution. The OS manager
employs greedy heuristics to match up the two system
call streams. If a new syscall does not match a previously-
recorded syscall in order, the OS manager creates new
system call actions, actors, and objects (as shown in Fig-
ure 4). Similarly, if a previously-recorded syscall does not
match the re-executed system calls in order, the OS man-
ager replaces the previously-recorded syscall’s actions
with no-ops. In the worst case, the only matches will be
the initial return from fork or exec, and the final syscall
invocation that terminates the process, potentially leading
to more re-execution, but not a loss of correctness.
In our running example, Eve trojans the texi2pdf
shell script by adding an extra line to start her botnet
worker. After repairing the texi2pdf file, RETRO re-
executes every process that ran the trojaned texi2pdf.
During shepherded re-execution of texi2pdf, exec sys-
tem calls to legitimate TEX programs are identical to
those during the original execution; in other words, the
system call argument objects are equivalent, and equiv on
the system call action returns true. As a result, there is no
need to re-execute these child processes. However, exec
system calls to Eve’s bot are missing, so the manager
replaces them with no-ops, which recursively undoes any
changes made by Eve’s bot.
5.3. Directory manager
The directory manager is responsible for exposing finer-
grained dependency information about directory entries.
Although the file system manager tracks changes to di-
rectories, it treats the entire directory as one inode, caus-
ing false dependencies in shared directories like /tmp.
The directory manager names each directory entry by
(device, part, inode, name). The first three components
of the name are the file system manager’s name for the
directory inode. The name part represents the file name
of the directory entry.
During normal operation, the directory manager must
record checkpoints of its objects, conceptually consist-
ing of the inode number for the directory entry (or L to
represent non-existent directory entries). However, since
the file system manager already records checkpoints of
all directories, the directory manager relies on the file
USENIX Association
USENIX Association
system manager’s checkpoints, and does not perform any
checkpointing of its own. The directory manager simi-
larly relies on the OS manager to record dependencies
between system call actions and directory entries accessed
by those system calls, such as name lookups in namei
(which incur a dependency from every directory entry
traversed), or directory modifications by rename (which
incur a dependency to the modified directory entries).
During repair, the directory manager’s sole responsibil-
ity is rolling back directory entries to a checkpoint; the
OS manager handles redo of all system calls. To roll back
a directory entry to an earlier checkpoint, the directory
manager finds the inode number contained in that direc-
tory entry (using the file system manager’s checkpoint),
and changes the directory entry in question to point to
that inode, with the help of RETRO’s kernel module. If
the directory entry did not exist in the checkpoint, the
directory manager similarly unlinks the directory entry.
5.4 System library managers
Every user login on a typical Unix system accesses sev-
eral system-wide files. For example, each login attempt
accesses the entire password file, and successful logins
update both the utmp file (tracking currently logged in
users) and the lastlog file (tracking each user’s last
login). In a naive system, these shared files can lead to
false dependencies, making it difficult to disambiguate
attacker actions from legitimate changes. To address this
problem, RETRO uses a libc system library manager to
expose the semantic independence between these actions.
One strawman approach would be to represent such
shared files much as directories (i.e., creating a separate
object for each user’s password file entry). However, un-
like the directory manager, which mediates all accesses to
a directory, a manager for a function in /ibc cannot guar-
antee that an attacker will not bypass it—the manager,
libc, and the attacker can be in the same address space.
Thus, the 1ibc manager does not change the representa-
tion of data objects, and instead simplifies re-execution,
by creating actors to represent the execution of individual
libc functions. For example, Figure 5 shows an actor for
the getpwnam function call as part of sshd.
During normal operation, the library manager cre-
ates a fresh actor for each function call to one of the
managed functions, such as getpwnam, getspnam, and
getgrouplist. The library manager names function
call actors by (bootgen, pid, pidgen, execgen, callgen);
the first four parts name the process, and callgen is a
unique ID for each function call. Much as with system
call actors, the arguments object contains the function
name and arguments, and the return object contains the
return value. Like processes, function call actors have
only one checkpoint, |, representing their initial state
prior to the call.
The library manager requires the OS manager’s help to
associate system calls issued from inside library functions
with the function call actor, instead of the process actor.
To do this, the OS manager maintains a “call stack” of
function call actors that are currently executing. On every
function call, the library manager pushes the new function
call actor onto the call stack, and on return, it pops the
call stack. The OS manager associates syscall invocation
and return actions with the last actor on the call stack, if
any, instead of the process actor.
During repair, the library manager’s rollback and redo
methods allow the repair controller to re-execute individ-
ual functions. For example, in Figure 5, the controller
will re-execute getpwnam, because its dependency on
/etc/passwd changed due to repair. However, if equiv
indicates the return value from getpwnam did not change,
the controller need not re-execute the rest of sshd.
RETRO’s trust assumption about the library manager
is that the function does not semantically affect the rest
of the program’s execution other than through its return
value. If an attacker process compromises its own libc
manager, this does not pose a problem, because the pro-
cess already depended on the attacker in other ways, and
RETRO will repair it. However, if an attacker exploits a
vulnerability in the function’s input parsing code (such as
a buffer overflow in getpwnam parsing /etc/passwd),
it can take control of getpwnam, and influence the ex-
ecution of the process in ways other than getpwnam’s
return value. Thus, RETRO trusts libc functions wrapped
by the library manager to safely parse files and faithfully
represent their return values.
5.5 Terminal manager
Undoing attacker’s actions during repair can result in
legitimate applications sending different output to a user’s
terminal. For example, if the userran 1s /tmp, the output
may have included temporary files created by the attacker,
or the 1s binary was trojaned by the attacker to hide
certain files. While RETRO cannot undo what the user
already saw, the terminal manager helps RETRO generate
compensating actions.
The terminal manager is responsible for objects repre-
senting pseudo-terminal, or pty, devices (/dev/pts/N in
Linux). During normal operation, the manager records
the user associated with each pty (with help from sshd),
and all output sent to the pty. During repair, if the output
sent to the pty differs from the output recorded during
normal operation, the terminal manager computes a text
diff between the two outputs, and emails it to the user.
5.6 Network manager
The network manager is responsible for compensating
for externally-visible changes. To this end, the network
manager maintains objects representing the outside world
(one object for each TCP connection, and one object for
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10) 99
100
each IP address/UDP port pair). During normal operation,
the network manager records all traffic, similar to the
terminal manager.
During repair, the network manager compares repaired
outgoing data with the original execution. When the
network manager detects a change in outgoing traffic, it
flags an external dependency, and presents the user or
administrator with three choices. The first choice is to
ignore the dependency, which is appropriate for network
connections associated with the adversary (such as Eve’s
login session in our running example, which will generate
different network traffic during repair). The second choice
is to re-send the network traffic, and wait for a response
from the outside world. This is appropriate for outgoing
network connections and idempotent protocols, such as
DNS. Finally, the third choice is to require the user to
manually resolve the external dependency, such as by
manually re-playing the traffic for incoming connections.
This is necessary if, say, the response to an incoming
SMTP connection has changed, the application did not
provide its own compensating action, and the user does
not want to ignore this dependency.
6 IMPLEMENTATION
We implemented a prototype of RETRO for Linux,* com-
ponents of which are summarized in Figure 7. During
normal execution, a kernel module intercepts and records
all system calls to a log file, implementing the runtime
half of the OS, file system, directory, terminal, and net-
work managers. To allow incremental loading of log
records, RETRO records an index alongside the log file
that allows efficient lookup of records for a given process
ID or inode number. The file system manager implements
checkpoints using subvolume snapshots in btrfs [37]. The
libc manager logs function calls using a new RETRO sys-
tem call to add ordered records to the system-wide log.
The repair controller, and the repair-time half of each
manager, are implemented as Python modules.
RETRO implements three optimizations to reduce log-
ging costs. First, it records SHA-1 hashes of data read
from files, instead of the actual data. This allows checking
for equivalence at repair time, but avoids storing the data
twice. Second, it does not record data read or written
by white-listed deterministic processes (in our prototype,
this includes gcc and 1d). This means that, if any of the
read or write dependencies to or from these processes are
suspected during repair, the entire process will have to
be re-executed, because individual read and write system
calls cannot be checked for equivalence or re-executed.
Since all of the dependency relationships are preserved,
this optimization trades off repair time for recording time,
3While our prototype is Linux-specific, we believe that RETRO’s
approach is equally applicable to other operating systems.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
Component
Logging kernel module
Repair controller, manager modules
System library managers
Backtracking GUI tool
| Lines of code
3,300 lines of C
5,000 lines of Python
700 lines of C
500 lines of Python
Figure 7: Components of our RETRO prototype, and an estimate of
their complexity, in terms of lines of code.
Objects repaired Objects repaired User
Attack with predicates without predicates | input
Proc Func File | Proc Func File
Password change 1 2 4 430 20 274 1
Log cleaning 59 0 40 60 0 40 0
Running example 58 57. 75 51361 300 1
sshd trojan 530 47 303 530 47 303 3
Figure 8: Repair statistics for the two honeypot attacks (top) and two
synthetic attacks (bottom). The repaired objects are broken down into
processes, functions (from libc), and files. Intermediate objects such as
syscall arguments are not shown. The concurrent workload consisted of
1,261 process, function, and file objects (both actor and data objects),
and 16,239 system call actions. RETRO was able to fully repair all
attacks, with no false positives or false negatives. User input indicate the
number of times RETRO asked for user assistance in repair; the nature
of the conflict is reported in §7.
but does not compromise completeness. Third, RETRO
compresses the resulting log files to save space.
7 EVALUATION
This section answers three questions about RETRO, in
turn. First, what kinds of attacks can RETRO recover
from, and how much user input does it require? Second,
are all of RETRO’s mechanisms necessary in practice?
And finally, what are the performance costs of RETRO,
both during normal execution and during repair?
7.1 Recovery from attack
To evaluate how RETRO recovers from different attacks,
we used three classes of attack scenarios. First, to make
sure we can repair real-world attacks, we used attacks
recorded by a honeypot. Second, to make sure RETRO
can repair worst-case attacks, we used synthetic attacks
designed to be particularly challenging for RETRO, in-
cluding the attack from our running example. For both
real-world and synthetic attacks, we perform user activity
described in the running example after the attack takes
place—namely, root logs in via ssh and adds an account
for Alice, who then also logs in via ssh to edit and build a
7px file. Finally, we compare RETRO to Taser, the state-
of-the-art attack recovery system, using attack scenarios
from the Taser paper [17].
Honeypot attacks. To collect real-world attacks, we
ran a honeypot [1] for three weeks, with a modified sshd
that accepted any password for login as root. Out of
many root logins, we chose two attacks that corrupted
our honeypot’s state in the most interesting ways.* In the
first attack, the attacker changed the root password. In the
second attack, the attacker downloaded and ran a Linux
4Most of the attackers simply ran a botnet binary or a port scanner.
USENIX Association
USENIX Association
Taser
Scenario Snapshot Nol NoIAN NoIANC RETRO User input required
Illegal storage FP FP FN FN v None.
Content destruction FP v v FN v None. (Generates terminal diff compensating action.)
Unhappy student FP FP v FN v None. (Generates terminal diff compensating action.)
Compromised database FP FP FP FN v None.
Software installation FP FP v v v Re-execute browser (or ignore browser state changes).
Inexperienced admin FP FP FP v v Skip re-execution of attacker’s login session.
Figure 9: A comparison of Taser’s four policies and RETRO against a set of scenarios used to evaluate Taser [17]. Taser’s snapshot policy tracks all
dependencies, Nol ignores IPC and signals, NoIAN also ignores file name and attributes, and NoIANC further ignores file content. FP indicates a
false positive (undoing legitimate actions), FN indicates a false negative (missing parts of the attack), and V indicates no false positives or negatives.
binary that scrubbed system log files of any mention of
the attacker’s login attempt.
For both of these attacks, RETRO was able to repair
the system while preserving all legitimate user actions, as
summarized in Figure 8. In the password change attack,
root was unable to log in after the attack, immediately
exposing the compromise, although we still logged in
as Alice and ran texi2pdf. In the second attack, all 59
repaired processes were from the attacker’s log cleaning
program, whose effects were undone.
For these real-world attacks, RETRO required minimal
user input. RETRO required one piece of user input to
repair the password change attack, because root’s login
attempt truly depended on root’s entry in /etc/passwd,
which was modified by the attacker. In our experiment,
the user told the network manager to ignore the conflict.
RETRO required no user input for the log cleaning attack.
Synthetic attacks. To check if RETRO can recover
from more insidious attacks, we constructed two synthetic
attacks involving trojans; results for both are summarized
in Figure 8. For the first synthetic attack, we used the
running example, where the attacker adds an account for
eve, installs a botnet and a backdoor PHP script, and tro-
jans the /usr/bin/texi2pdf shell script to restart the
botnet. Legitimate users were unaware of this attack, and
performed the same actions. Once the administrator de-
tected the attack, RETRO reverted Eve’s changes, includ-
ing the eve account, the bot, and the trojan. As described
in §5.2.3, RETRO used shepherded re-execution to undo
the effects of the trojan without re-running the bulk of the
trojaned application. As Figure 8 indicates, RETRO re-
executed several functions (getpwnam) to check if remov-
ing eve’s account affected any subsequent logins. One
login session was affected—Eve’s login—and RETRO’s
network manager required user input to confirm that Eve’s
login need not be re-executed.
One problem we discovered when repairing the running
example attack is that the UID chosen for Alice by root’s
useradd alice command depends on whether eve’s ac-
count is present. If RETRO simply re-executed useradd
alice, useradd would pick a different UID during re-
execution, requiring RETRO to re-execute Alice’s entire
session. Instead, we made the useradd command part of
the system library manager, so that during repair, it first
tries to re-execute the action of adding user alice under
the original UID, and only if that fails does it re-execute
the full useradd program. This ensures that Alice’s UID
remains the same even after RETRO removes the eve
account (as long as Alice’s UID is still available).
A second synthetic attack we tried was to trojan
/usr/sbin/sshd. In this case, users were able to log
in as usual, but undoing the attack required re-executing
their login sessions with a good sshd binary. Because
RETRO cannot rerun the remote ssh clients (and a new key
exchange, resulting in different keys, makes TCP-level
replay useless), RETRO’s network manager asks the ad-
ministrator to redo each ssh session manually. Of course,
this would not be practical on a real system, and the ad-
ministrator may instead resort to manually auditing the
files affected by those login sessions, to verify whether
they were affected by the attack in any way. However, we
believe it is valuable for RETRO to identify all connections
affected by the attack, so as to help the administrator lo-
cate potentially affected files. In practice, we hope that an
intrusion detection system can notice such wide-reaching
attacks; after a few user logins, the dependency graph
indicates that unrelated user logins are all dependent on a
previous login session, which an IDS may be able to flag.
Taser attacks. Finally, we compare RETRO to the state-
of-the-art intrusion recovery system, Taser, under the
attack scenarios that were used to originally evaluate
Taser [17]. Figure 9 summarizes the results.
In the first scenario, illegal storage, the attacker creates
a new account for herself, stores illegal content on the
system, and trojans the 1s binary to mask the illegal
content. RETRO rolls back the account, illegal files, and
the trojaned 1s binary, and uses the legitimate 1s binary to
re-execute all 1s processes from the past. Even though the
trojaned 1s binary hid some files, the legitimate 1s binary
produces the same output, because RETRO removes the
hidden files during repair. As a result, there is no need
to notify the user. If 1s’s output did change, the terminal
manager would have sent a diff to the affected users.
In the content destruction scenario, an attacker deletes
a user’s files. Once the user notices the problem, he
uses RETRO to undo the attack. After recovering the
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI’10) 101
102
Without RETRO With RETRO
Workload Log size Snapshot size | #ofobjects # of actions
1 core 1 core 2 cores
Kernel build 295 sec 557 sec 351 sec 761 MB 308 MB 87,405 5,698,750
Web server 7260 req/s 3195 req/s 5453 req/s 98 MB 272 KB 508 185,315
HotCRP 20.4 req/s 15.1 req/s 20.0 req/s 81 MB 27 MB 19,969 939,418
Figure 10: Performance and storage costs of RETRO for three workloads: building the Linux kernel, serving files as fast as possible using Apache [2]
for 1 minute, and simulating requests to HotCRP [23] from the 30 minutes before the SOSP 2007 deadline, which averaged 2.1 requests per
second [44] (running as fast as possible, this workload finished in 3-4 minutes). “# of objects” reflects the number of files, directory entries, and
processes; not included are intermediate objects such as system call arguments. “# of actions” reflects the number of system call actions.
files, RETRO generates a terminal output diff for the login
session during which the user noticed the missing files
(after repair, the user’s 1s command displays those files).
In the unhappy student scenario, a student exploits an
f£tpd bug to change permissions on a professor’s grade
file, then modifies the grade file in another login session,
and finally a second accomplice user logs in and makes a
copy of the grade file. In repairing the attack, RETRO rolls
back the grade file and its permissions, re-executes the
copy command (which now fails), and uses the terminal
manager to generate a diff for the attackers’ sessions,
informing them that their copy command now failed.
In the compromised database scenario, an attacker
breaks into a server, modifies some database records (in
our case we used SQLite), and subsequently a legitimate
user logs in and runs a script that updates database records
of its own. RETRO rolls back the database file to a state
before the attack, and re-executes the database update
script to preserve subsequent changes, with no user input.
In the software installation scenario, the administrator
installs the wrong browser plugin, and only detects this
problem after running the browser and downloading some
files. During repair, RETRO rolls back the incorrect plu-
gin, and attempts to repair the browser using re-execution.
Since RETRO encounters external dependencies in re-
executing network applications, it requests the user to
manually redo any interactions with the browser. In our
experiment, the user ignored this external dependency,
because he knew the browser made no changes to local
state worth preserving.
In the inexperienced admin scenario, root selects a
weak password for a user account, and an attacker guesses
the password and logs in as the user. Undoing root’s pass-
word change affects the attacker’s login session, requiring
one user input to confirm to the network manager that it’s
safe to discard the attacker’s TCP connection.
In summary, RETRO correctly repairs all six attack
scenarios posed by Taser, requiring user input only in two
cases: to re-execute the browser, and to confirm that it’s
safe to drop the attacker’s login session. Taser requires
application-specific policies to repair these attacks, and
some attacks cannot be fully repaired under any policy.
Taser’s policies also open up the system to false negatives,
allowing an adversary to bypass Taser altogether.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
7.2 Technique effectiveness
In this subsection, we evaluate the effectiveness of
RETRO’Ss specific techniques, including re-execution,
predicate checking, and refinement.
Re-execution is key to preserving legitimate user ac-
tions. As described in §7.1 and quantified in Figure 8,
RETRO re-executes several processes and functions to pre-
serve and repair legitimate changes. Without re-execution,
RETRO would have to conservatively roll back any files
touched by the process in question, much like Taser’s
snapshot policy, which incurs false positives.
Without predicates, RETRO would have to perform
conservative dependency propagation in the dependency
graph. As in Taser, dependencies on attack actions
quickly propagate to most objects in the graph, requir-
ing re-execution of almost every process. This leads
to re-execution of sshd, which requires user assistance.
Figure 8 shows that many of the objects repaired with-
out predicates were not repaired with predicates enabled.
Taser would roll back all of these objects (false positives).
Thus, predicates are an important technique to minimize
user input due to re-execution.
Without refinement of actor and data objects,
RETRO would incur false dependencies via /tmp and
/etc/passwd. As Figure 8 shows, several functions
(such as getpwnam) were re-executed in repairing from
attacks. If RETRO was unable to re-execute just those
functions, it would have re-executed processes like sshd,
forcing the network manager to request user input. Thus,
refinement is important to minimizing user input due to
false dependencies.
7.3 Performance
We evaluate RETRO’s performance costs in two ways.
First, we consider costs of RETRO’s logging during nor-
mal execution. To this end, we measure the CPU overhead
and log size for several workloads. Figure 10 summarizes
the results. We ran our experiments on a 2.8GHz Intel
Core i7 system with 8 GB RAM running a 64-bit Linux
2.6.35 kernel, with either one or two cores enabled.
The worst-case workload for RETRO is a system that
uses 100% of CPU time and spends most of its time com-
municating between small processes. One such extreme
workload is a system that continuously re-builds the Linux
kernel; another example is an Apache server continuously
USENIX Association
USENIX Association
serving small static files. For such systems, RETRO in-
curs a 89-127% CPU overhead using a single core, and
generates about 100-150 GB of logs per day. A 2 TB
disk ($100) can store two weeks of logs at this rate before
having to garbage-collect older log entries. If a spare
second core is available, and the application cannot take
advantage of it, it can be used for logging, resulting in
only 18-33% CPU overhead.
For a more realistic application, such as a HotCRP [23]
paper submission web site, RETRO incurs much less
overhead, since HotCRP’s PHP code is relatively CPU-
intensive. If we extrapolate the workload from the 30
minutes before the SOSP 2007 deadline [44] to an entire
day, HotCRP would incur 35% CPU overhead on a single
core (and almost no overhead if an additional unused core
were available), and use about 4 GB of log space per day.
We believe that these are reasonable costs to pay to be
able to recover integrity after a compromise of a paper
submission web site.
Second, we consider the time cost of repairing a sys-
tem using RETRO after an attack. As Figure 8 illustrated,
RETRO is often effective at repairing only a small subset
of objects and actions in the action history graph, and for
attacks that affect the entire system state, such as the sshd
trojan, user input dominates repair costs. To illustrate the
costs of repairing a subset of the action history graph,
we measure the time taken by RETRO to repair from a
micro-benchmark attack, where the adversary adds an
extraneous line to a log file, which is subsequently mod-
ified by a legitimate process. When only this attack is
present in RETRO’s log (consisting of 10 process objects,
126 file objects, and 399 system call actions), repair takes
0.3 seconds. When this attack runs concurrently with a
kernel build (as shown in Figure 10), repair of the attack
takes 4.7 seconds (10x longer), despite the fact that the
log is 10,000 larger. This shows that RETRO’s log in-
dexing makes repair time depend largely on the number
of affected objects, rather than the overall log size.
8 DISCUSSION AND FUTURE WORK
An important assumption of RETRO is that the attacker
does not compromise the kernel. Unfortunately, security
vulnerabilities are periodically discovered in the Linux
kernel [5, 6], making this assumption potentially danger-
ous. One solution may be to use virtual machine based
techniques [14, 21], although it is difficult to distinguish
kernel objects after a kernel compromise. We plan to
explore ways of reducing trust in future work.
In our current prototype, if attackers compromise the
kernel and obtain access to RETRO’s log files, they may
be able to extract sensitive information, such as user pass-
words or keys, that would not have been persistently
stored on a system without RETRO. One possible so-
lution may be to encrypt the log files and checkpoints,
so that the administrator must reboot the system from a
trusted CD and enter the password to initiate recovery.
Our current prototype can only repair the effects of an
attack on a single machine, and relies on compensating
actions to repair external state. In future work, we plan
to explore ways to extend automated repair to distributed
systems, perhaps based on the ideas from [29, 42].
RETRO requires the system administrator to specify
the initial intrusion point in order to undo the effects
of the attack, and finding the initial intrusion point can
be difficult. In future work, we hope to leverage the
extensive data available in RETRO’s dependency graph
to build intrusion detection tools that can better pin-point
intrusions. Alternatively, instead of trying to pinpoint
the attack, we may be able to use RETRO to retroactively
apply security patches into the past, and re-execute any
affected computations, thus eliminating any attacks that
exploited the vulnerability in question.
We did not have space to address several practical as-
pects of using RETRO, such as performing multiple re-
pairs or undoing a repair. These operations translate into
making additional checkpoints, and updating the graph
accordingly after repair. Also, as hinted at in §5, we plan
to explore the use of more specialized repair managers,
such as managers for a language runtime, a database, or
an application like a web server or web browser. Finally,
while RETRO’s performance and storage overheads are
already acceptable for some workloads, we plan to further
reduce them by not logging intermediate dependencies
that can be reconstructed at repair time.
9 CONCLUSION
RETRO repairs system integrity from past attacks by using
an action history graph to track system-wide dependen-
cies, roll back affected objects, and re-execute legitimate
actions affected by the attack. RETRO minimizes user
input by avoiding re-execution whenever possible, and
by using compensating actions for external dependencies.
RETRO’s key techniques for minimizing re-execution in-
clude predicates, refinement, and shepherded re-execution.
A prototype of RETRO for Linux recovers from a mix of
ten real-world and synthetic attacks, repairing all side-
effects of the attack in all cases. Six attacks required no
user input to repair, and RETRO required significant user
input in only two cases involving trojaned network-facing
applications.
ACKNOWLEDGMENTS
We thank Victor Costan, Robert Morris, Jacob Strauss, the
anonymous reviewers, and our shepherd, Adrian Perrig,
for their feedback. Quanta Computer partially supported
this work. Taesoo Kim is partially supported by the Sam-
sung Scholarship Foundation, and Nickolai Zeldovich is
partially supported by a Sloan Fellowship.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) —-103
104
REFERENCES
{1
[2
[3
[4
[5
[6
[7
[9
10
11
12
13
15
16
18
19
20
21
22
23
The Honeynet Project. http: //www.honeynet.org/.
Apache web server, May 2010. http: //httpd.apache.org/.
P. Ammann, S. Jajodia, and P. Liu. Recovery from malicious trans-
actions. [EEE Transactions on Knowledge and Data Engineering,
14(5):1167-1185, 2002.
Apple Inc. What is Mac OS X - Time Machine.
http://www.apple.com/macosx/what-is-macosx/
time-machine.html.
J. Arnold and M. F. Kaashoek. Ksplice: Automatic rebootless ker-
nel updates. In Proc. of the ACM EuroSys Conference, Nuremberg,
Germany, Mar 2009.
J. Arnold, T. Abbott, W. Daher, G. Price, N. Elhage, G. Thomas,
and A. Kaseorg. Security impact ratings considered harmful. In
Proc. of the 12th Workshop on Hot Topics in Operating Systems,
Monte Verita, Switzerland, May 2009.
AVG Technologies. Why traditional anti-malware solutions are no
longer enough. http: //download.avg.com/filedir/other/
pf_wp-90_A4_us_z3162_20091112.pd£, Oct 2009.
] K.J. Biba. Integrity considerations for secure computer systems.
Technical Report MTR-3153, MITRE Corp., Bedford, MA, Apr
1977.
U. Braun, A. Shinnar, and M. Seltzer. Securing provenance. In
Proc. of the 3rd Usenix Workshop on Hot Topics in Security, San
Jose, CA, Jul 2008.
A. B. Brown and D. A. Patterson. Undo for operators: Building
an undoable e-mail store. In Proc. of the 2003 Usenix ATC, pages
1-14, San Antonio, TX, Jun 2003.
R. Chandra, N. Zeldovich, C. Sapuntzakis, and M. Lam. The
Collective: A cache-based system management architecture. In
Proc. of the 2nd NSDI, pages 259-272, Boston, MA, May 2005.
CheckPoint, Inc. IPS-1 intrusion detection and prevention system.
http://www.checkpoint.com/products/ips-1/.
J. Corbet. A checkpoint/restart update. http://lwn.net/
Articles/375855/, Feb 2010.
G. W. Dunlap, S. T. King, S. Cinar, M. Basrai, and P. M. Chen.
ReVirt: Enabling intrusion analysis through virtual-machine log-
ging and replay. In Proc. of the 5th OSDI, pages 211-224, Boston,
MA, Dec 2002.
S. Forrest, S. Hofmeyr, and A. Somayaji. The evolution of system-
call monitoring. In Proc. of the 2008 Annual Computer Security
Applications Conference, pages 418-430, Dec 2008.
FreeBSD. What is securelevel?
org/doc/en_US.1S08859-1/books/faq/security.html#
SECURELEVEL.
] A. Goel, K. Po, K. Farhadi, Z. Li, and E. D. Lara. The Taser
intrusion recovery system. In Proc. of the 20th ACM SOSP, pages
163-176, Brighton, UK, Oct 2005.
B. Harder. Microsoft Windows XP system restore. http:
//msdn.microsoft.com/en-us/library/ms997627.aspx,
Apr 2001.
A. Joshi, S. King, G. Dunlap, and P. Chen. Detecting past and
present intrusions through vulnerability-specific predicates. In
Proc. of the 20th ACM SOSP, pages 91-104, Brighton, UK, Oct
2005.
G. H. Kim and E. H. Spafford. The design and implementation
of Tripwire: A file system integrity checker. In Proc. of the 2nd
ACM CCS, pages 18-29, Fairfax, VA, Nov 1994.
S. T. King and P. M. Chen. Backtracking intrusions. ACM TOCS,
23(1):5 1-76, Feb 2005.
S. T. King, Z. M. Mao, D. G. Lucchetti, and P. M. Chen. Enriching
intrusion alerts through multi-host causality. In Proc. of the 12th
NDSS, San Diego, CA, Feb 2005.
E. Kohler. Hot crap! In Proc. of the Workshop on Organizing
Workshops, Conferences, and Symposia for Computer Systems,
San Francisco, CA, Apr 2008.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
http://www. freebsd.
[24]
[25]
[26]
[27]
[28]
[29]
[30]
[31]
[32]
[33]
[34]
[35]
[36]
[37]
[38]
[39]
[40]
[41]
[42]
[43]
[44]
[45]
C. Kolbitsch, P. M. Comparetti, C. Kruegel, E. Kirda, X. Zhou,
and X. Wang. Effective and efficient malware detection at the end
host. In Proc. of the 18th Usenix Security Symposium, Montreal,
Canada, Aug 2009.
M. Krohn, A. Yip, M. Brodsky, N. Cliffer, M. F. Kaashoek,
E. Kohler, and R. Morris. Information flow control for standard
OS abstractions. In Proc. of the 21st ACM SOSP, pages 321-334,
Stevenson, WA, Oct 2007.
A. Lewis. LYM HOWTO: Snapshots. http: //www.tldp.org/
HOWTO/LVM-HOWTO/snapshotintro.html.
P. Liu, P. Ammann, and S. Jajodia. Rewriting histories: Recovering
from malicious transactions. Journal of Distributed and Parallel
Databases, 8(1):7—40, 2000.
P. Loscocco and S. Smalley. Integrating flexible support for secu-
rity policies into the Linux operating system. In Proc. of the 2001
Usenix ATC, pages 29-40, Jun 2001. Freenix track.
P. Mahajan, R. Kotla, C. C. Marshall, V. Ramasubramanian, T. L.
Rodeheffer, D. B. Terry, and T. Wobber. Effective and efficient
compromise recovery for weakly consistent replication. In Proc.
of the ACM EuroSys Conference, pages 131-144, Nuremberg,
Germany, Mar 2009.
Microsoft. How to use the roll back driver feature in Windows XP.
http://support .microsoft .com/kb/283657, Aug 2007.
MokaFive, Inc. Mokafive, virtual desktops for businesses and
personal use. http: //www.mokafive.com/.
NetApp. Snapshot. http: //www.netapp.com/us/products/
platform-os/snapshot.html.
E. B. Nightingale, P. M. Chen, and J. Flinn. Speculative execution
in a distributed file system. In Proc. of the 20th ACM SOSP,
Brighton, UK, Oct 2005.
R. Paleari, L. Martignoni, E. Passerini, D. Davidson, M. Fredrik-
son, J. Giffin, and S. Jha. Automatic generation of remediation
procedures for malware infections. In Proc. of the 19th Usenix
Security Symposium, Washington, DC, Aug 2010.
J. S. Plank, M. Beck, G. Kingsley, and K. Li. Libckpt: Transparent
checkpointing under Unix. In Proc. of the 1995 Usenix ATC, pages
213-223, New Orleans, LA, Jan. 1995.
D. E. Porter, O. S. Hofmann, C. J. Rossbach, A. Benn, and
E. Witchel. Operating systems transactions. In Proc. of the 22nd
ACM SOSP, pages 161-176, Big Sky, MT, Oct 2009.
O. Rodeh. B-trees, shadowing, and clones. ACM Transactions on
Storage, 3(4):1-27, 2008.
M. Satyanarayanan. Scalable, secure and highly available file
access in a distributed workstation environment. JEEE Computer,
pages 9-21, May 1990.
A. Seshadri, M. Luk, N. Qu, and A. Perrig. SecVisor: A tiny
hypervisor to provide lifetime kernel code integrity for commodity
OSes. In Proc. of the 21st ACM SOSP, Stevenson, WA, Oct 2007.
F. Shafique, K. Po, and A. Goel. Correlating multi-session attacks
via replay. In Proc. of the Second Workshop on Hot Topics in
System Dependability, Seattle, WA, Nov 2006.
B. Spengler. grsecurity. http: //www.grsecurity.net/.
P. Vogt, F. Nentwich, N. Jovanovic, E. Kirda, C. Kruegel, and
G. Vigna. Cross site scripting prevention with dynamic data
tainting and static analysis. In Proc. of the 14th NDSS, San Diego,
CA, Feb-Mar 2007.
H. Yin, D. Song, M. Egele, C. Kruegel, and E. Kirda. Panorama:
capturing system-wide information flow for malware detection
and analysis. In Proc. of the 14th ACM CCS, Alexandria, VA,
Oct-Nov 2007.
A. Yip, X. Wang, N. Zeldovich, and M. F. Kaashoek. Improving
application security with data flow assertions. In Proc. of the 22nd
ACM SOSP, pages 291-304, Big Sky, MT, Oct 2009.
N. Zeldovich, S. Boyd-Wickizer, E. Kohler, and D. Maziéres.
Making information flow explicit in HiStar. In Proc. of the 7th
OSDI, pages 263-278, Seattle, WA, Nov 2006.
USENIX Association
USENIX Association
Static Checking of Dynamically- Varying Security Policies in
Database-Backed Applications
Adam Chlipala
Impredicative LLC
Abstract
We present a system for sound static checking of security
policies for database-backed Web applications. Our tool
checks a combination of access control and information
flow policies, where the policies vary based on database
contents. For instance, one or more database tables may
represent an access control matrix, controlling who may
read or write which cells of these and other tables. Us-
ing symbolic evaluation and automated theorem-proving,
our tool checks these policies statically, requiring no pro-
gram annotations (beyond the policies themselves) and
adding no run-time overhead. Specifications come in the
form of SQL queries as policies: for instance, an appli-
cation’s confidentiality policy is a fixed set of queries,
whose results provide an upper bound on what infor-
mation may be released to the user. To provide user-
dependent policies, we allow queries to depend on what
secrets the user knows. We have used our prototype im-
plementation to check several programs representative of
the data-centric Web applications that are common today.
1 Introduction
Much of today’s most important software exists as
Web applications, and many of these applications are
thin interface layers for relational databases. Real-
world requirements impel developers to implement many
application-specific schemes for access control (“who
can do what?’’) and information flow (“who can learn
what?’’). To reason about correctness of these implemen-
tations, the programmer must consider all possible flows
of control through a program.
This task is hard enough if a security policy can be
expressed statically, as, for instance, a list of which of
a fixed set of principals is allowed to perform each of a
fixed set of actions. However, the needs of real applica-
tions tend to force use of evolving security policies, and
usually the most convenient place to store a policy is in
the same database where the rest of application data re-
sides. For instance, a database often encodes some kind
of access control matrix, where entries reference rows of
other tables. The peculiar structure of an organization
may require access control based on customized schema
design and checking code. An effective security valida-
tion tool must be able to “understand” these policies.
Many program analysis and instrumentation schemes
have been applied to provide some automatic assurance
of security properties. In this space, the traditional di-
chotomy is between dynamic and static tools, based on
whether checking happens at run time or compile time.
The two extremes have their characteristic advantages.
e Dynamic analysis can often be implemented with-
out requiring any program annotations included
solely to make analysis easier.
e Real developers have an easier time writing spec-
ifications compatible with dynamic analysis, since
these specifications can often be arbitrary code for
inspecting program states.
e Static analysis can provide strong guarantees that
hold for all possible program executions, even those
exercising weird corner cases that may not have
been considered.
e Static analysis adds no run-time overhead.
In this paper, we present a tool UrFlow for static anal-
ysis of database-backed Web applications. We have tried
to reap some of all of the advantages just described. Our
tool requires no program annotations and provides fully
sound static assurance about all possible executions of a
program, and it requires no changes to the run-time be-
havior of programs. We take advantage of the fact that
it is already common for Web applications to be imple-
mented at quite a high level, relying on an SQL engine
to implement the key data structures. Our tool models
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) —-:105
106
the semantics of SQL faithfully, at a level that makes for-
mal, automated analysis quite practical. We use popular
ideas from symbolic execution and automated theorem-
proving to build detailed models of program behavior
automatically, which saves developers the trouble of ex-
plaining these models with code annotations.
It is natural for developers to write specifications that
look much like the program code they are already writ-
ing. Traditional assertions (e.g., with the C assert
macro) fall under this heading. In an application that de-
pends on an SQL engine to manage its main data struc-
tures, it seems similarly natural to express security poli-
cies using SQL. Our tool is based on that model, allowing
developers to write detailed statically-checkable specifi-
cations without learning a new language. Queries can
express confidentiality properties by selecting which in-
formation the user may learn, and queries can express
database update properties by selecting allowable state
transitions. We need only one extension to the standard
SQL syntax and semantics: to allow policies to vary by
user, we introduce explicit consideration of which secrets
(e.g., passwords) the user knows.
UrFlow is integrated with the compiler for Ur/Web [3],
a domain-specific language for Web application develop-
ment. Ur/Web presents a very high-level view of the do-
main, with explicit language support for the key elements
of Web applications. For instance, the SQL interface uses
an expressive type system to ensure that any code that
type-checks accesses the SQL database correctly. In the
present project, we have used the first-class SQL support
to avoid the need for program analysis to recover a high-
level view of how an application uses the database.
We begin by introducing our policy model and demon-
strating its versatility. After that, we present our pro-
gram analysis, including its symbolic evaluation and au-
tomated theorem-proving aspects. Next, we discuss the
scope and limitations of our analysis, describe some
case-study applications that we have checked with Ur-
Flow, and compare with related work.
2 SQL Queries as Policies
Consider a simple application that maintains a database
of users and per-user secret strings. We can declare our
schema to Ur/Web with table declarations. Following
standard practice in relational databases, each table in-
cludes a unique integer ID, which provides a convenient
handle to pass to row-specific operations. Besides an ID,
a user record contains a username and password, and
a secret record contains the owning user ID and the
data value.
{ Id
Pass
table user int, Nam string,
string }
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
table secret : { Id
Data
int, User int,
string }
We also declare an HTTP cookie, which acts like a
typed global variable which exists separately on each
Web browser. This cookie tracks the authentication in-
formation for the currently logged-in user. While a more
realistic program would probably rely on unique session
IDs, here we adopt the less secure strategy of storing a
user ID and password pair in each cookie, to simplify the
example.
cookie login { Id int, Pass string }
We can write a function that checks this cookie and
returns its user ID if the password is correct. The code
is written in a functional style, where we collapse “ex-
pressions” and “statements” into a single syntactic class.
Thus, instead of determining the function return value
with explicit return statements, we just say that the
function result is the value of the single expression that
is the function body.
Ur/Web code makes a lot of use of tagged unions, a
safe analogue to C unions that is popular in functional
programming languages. A tagged union value is either a
simple tag, which is like an enum value in C; or a pairing
of a tag and another value, which is like a C union, but
with a convention to ensure that it is always possible to
inspect a value and determine which union alternative is
being used. For tag T, a simple tag expression is written
like T, while the pairing of that tag with expression e is
written T(e). For instance, instead of allowing every
object type to be inhabited by a special value null, we
instead represent null with an explicit tag None, and
we represent non-null object o as Some (0) . A pattern-
matching construct case is used to deconstruct tagged
union values.
Here is the code for a function to check the correct-
ness of the information in the login cookie. It is writ-
ten in a compiler intermediate language in which some
higher-order functional programming idioms have been
replaced with more standard imperative code.
fun userId() =
case getCookie(login) of
None => None
| Some (li) =>
let b = query
(SELECT COUNT(«) > 0 AS B
FROM user
WHERE user.Id = {li.Id}
AND user.Pass = {1li.Pass})
(cr acc => r.B) False in
if b then
Some (1i.Id)
else
error("Wrong user ID or password!")
USENIX Association
USENIX Association
Our userTd function begins by retrieving the current
value of the Login cookie. This will either be None,
if no value of the cookie is set; or Some (1i), if the
ID/password record 1i has been set as the cookie value.
If the cookie is not set, there is no user ID to return. Oth-
erwise, we must consult the database to see if the pass-
word is correct.
We have literal SQL syntax embedded in the code,
with splicing of variable values using curly braces. The
query checks if there are any rows in the user table
matching the cookie contents. In this intermediate lan-
guage, every database read is expressed as a loop over
the results of a query. The body of the loop is written as
an expression with two explicitly-named new local vari-
ables: r, the latest row to process; and acc, an accu-
mulator that is modified as we process rows. The body
expression after the => determines the new accumulator
value after every iteration. We give False as the initial
accumulator value. In our example here, the loop body
ignores the accumulator, and we simply project the one
field of any result row to save as the accumulator. The
error function aborts program execution with an error
message, which we do here when the user provides in-
valid credentials.
We can write the main entry point of our application
to display all of the logged-in user’s secrets.
fun main() =
case userlId() of
None => write("You’re not logged in.")
| Some (u) =>
query (SELECT secret.Id, secret.Data
FROM secret
WHERE secret.User = {u})
(r acc =>
write ("<li> <i>");
write (toString (r.Secret.Id));
write("</i>: ");
write (escape (r.Secret.Data));
write("</li>")) ()
In this query loop, the accumulator is still ignored, and
in fact we execute the function body solely for its side
effects, which involve writing HTML to be sent to the
client.
We would like to verify that this application satisfies
a reasonable confidentiality policy. Intuitively, every cell
of the database belongs to a particular user. We want to
ensure that no user is able to read cells belonging to a
different user. This simple policy expresses our intent
for the cells of the user table.
policy sendClient (SELECT *
FROM user
WHERE known (user.Pass) )
The informal meaning of this policy is that the user
may learn any value that could be returned from this
query. Every policy statement is followed by a key-
word naming a kind of policy. In this case, that keyword
is sendClient, which is used for confidentiality poli-
cies. Specifically, the user may learn anything about any
row of user whose password he knows. The new pred-
icate known models which information the client is al-
ready aware of. We assume the client knows the text of
the program and the text of the HTTP request it sent. In
our example, when we disclose any secret information,
we know that the user’s own password is known because
it came from the login cookie, which was part of the
incoming HTTP request.
A more complicated policy allows the release of infor-
mation about secrets.
policy sendClient (SELECT +*
FROM secret, user
WHERE secret.User = user.Id
AND known (user.Pass) )
We use a join between the secret and user tables,
requiring that the client demonstrate knowledge of the
password for the user who owns the secret.
Our tool verifies that the application satisfies these se-
curity policies. That is, every cell of the database whose
value might be disclosed could have been selected by one
of these queries, based on an interpretation of known
drawn from the HTTP request that prompted an execu-
tion.
There are several opportunities for mistakes in imple-
menting the policy. Consider what would happen if we
had implemented userId to always return 17. When
we run the compiler, we get an error message. The com-
piler tells us which secret may be leaked, and (in addition
to the location of the offending write) we are given a first-
order logic characterization of the state of the program at
the time when the leak might occur.
User learns: r.Secret.Data
Hypotheses: secret (xl),
r = {Secret =
{Id = xl.Id, Data = x1.Data}},
x1l.User = 17
The hypotheses are generated directly from the SQL
query in main. The first hypothesis tells us that row x1
is in the secret table. Our row variable r is equated
with a record built by projecting the requested fields from
x1, and the last hypothesis represents the WHERE clause.
In the correct implementation, UrFlow explores every
static path through the program, maintaining a logical
state at each point. When the analysis reaches the point
that triggered the error above, we have this more infor-
mative state.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) —-107
108
c = cookie/login, known(c),
c = Some(c2), user(xl),
xl.Id = c2.Id, xl.Pass = c2.Pass,
secret (x2), x2.User = c2.I1d,
{Secret = {Id = x2.Id, Data =
r= x2.Data}}
The variable c stands for the cookie value, which is
asserted to be known to the user. The SQL query from
userId is reflected with assertions about a variable x1,
which is the row of user that must have matched the
query for execution to reach this point. The confiden-
tiality policy used a join between secret and user to
describe when information on secrets may be released.
The program code, on the other hand, contains no joins.
UrFlow understands join semantics to the point where it
is able to deduce that the above logical state implies that
a join, performed as in the policy, would authorize the
release of everything included in the record r.
2.1 What is Being Checked?
We can give a simple characterization of exactly what
confidentiality property the analyzer enforces, as a func-
tion of the policy the user specifies. First, we need to
define exactly what we mean by the known predicate. In-
formally, a known piece of data is something that the user
is already aware of, so that no confidentiality require-
ment is violated by echoing back that value or another
value derived from it in a predictable way. More for-
mally, known is the most restrictive predicate satisfying
the following rules:
1. Any constant appearing in the program text is
known.
2. The initial value of every cookie is known. These
cookies may have arbitrary structured types, as in
the record type given to the Login cookie in the
last example.
3. The value of every explicit parameter to the appli-
cation is known. For page requests generated by
submission of HTML forms, this includes all form
field values.
4. Arecord is known iff all of its fields are known.
5. For any union tag T (e.g., Some in our example), a
value v is known iff T (v) is known.
We say that a value v is allowed in a specific database
state D if there exists a sendClient policy that, when
executed in state D, would return v as one of its outputs.
We say that a value v is built from a set S if v is in S
or can be constructed out of the elements of S by com-
bining a subset of them with record and tagged union
operations.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
Now we can give a concise description of exactly what
UrFlow checks. For any execution of a program that the
analysis approved:
1. Whenever a write command sends some value v
to the client, v is built from the set of values that are
known or allowed.
2. Whenever the program branches based on the value
v of some test expression, such that the branch cho-
sen influences what might be sent to the client later,
v is built from the set of values that are known or
allowed. This prevents some implicit flows, where
the very fact that a program reaches a particular line
of code may reveal secret information. Since im-
plicit flows are a notorious source of false alarms in
information flow analysis, programmers might want
to turn off this piece of checking, which would be
easy to do via a compiler flag.
The same kind of characterization does not work well
for ruling out implicit flows induced by SQL WHERE
clauses, so we leave additional checking of that kind for
future work. This means that a checked program may
leak information about the existence of rows, based on
tests against arbitrary SQL expressions, but the contents
of those rows will not be leaked directly.
2.2 Authorizing Database Writes
UrFlow also checks every database modification. For
example, consider this page generation function, which
would be given as the action to run upon submission of
an HTML form for adding a new secret.
fun addSecret (fields) =
case userlId() of
None => write("You’re not logged in.")
| Some u =>
let id = nextId() in
dml (INSERT INTO secret
VALUES ({id}, {u},
main ()
(Id, User,
If we do not assert an explicit database update policy,
then UrFlow rejects this program. Here is one policy that
would allow the insertion:
policy mayInsert (SELECT *
FROM secret AS New, user
WHERE New.User = user.Id
AND known (user.Pass)
AND known (New.Data) )
We reuse the same SQL query notation for modifica-
tion policies, though the choice of SELECT clause is ig-
nored, so we will always write SELECT *. One of the
Data)
{fields.Data}));
USENIX Association
USENIX Association
tables in the FROM clause must be given the name New;
this is the table for which we are authorizing insertion.
UrFlow only allows a row insertion if the new row
could be returned by one of the mayInsert queries,
in a certain sense. In checking against a particular policy
query, we interpret the New relation as the universal rela-
tion, containing all possible tuples. The policy may join
it with other, real database tables and perform filtering
with WHERE, leading to a result set of rows that may be
infinite. The insertion is permitted if the New part of one
of these rows matches the values being inserted.
Our insertion policy lets any user add secrets if he as-
sociates them with his own user. We can also authorize
deletions and updates, based on similar criteria.
policy mayDelete (SELECT *
FROM secret AS Old, user
WHERE Old.User = user.Id
AND known (user.Pass) )
policy mayUpdate (SELECT «
FROM secret AS Old, secret AS New, user
WHERE Old.User = user.Id
AND New.User = Old.User
AND New.Id = Old.Id
AND known (user.Pass)
AND known (New.Data) )
A mayDelete policy must tag a FROM table as 01d,
to stand for the table being deleted from. A mayUpdate
policy needs both O1d and New tables, standing for the
part of a table being updated and the new data being writ-
ten into it. Both new policies retain the logic for checking
that the client knows the password for the user whose se-
cret is affected, and the update policy also requires that
the secret ID is not changed. The insertion and update
policies require that the new data value is known, which
provides a simple guard against inadvertent leaking of
privileged information into a part of the database that is
considered to be less privileged.
3 Flexibility of Query-Based Policies
We have found that this approach to writing specifica-
tions leads to natural descriptions of many natural poli-
cies. For instance, we have implemented a simple Web
message forum system. Our implementation contains a
table representing an access-control list. Each entry gives
a user permissions in a specific forum, at a particular nu-
meric level of access.
table acl forumld,
userlId, Level
{ Forum
User int }
One policy allows release of information about any
message in a forum that the current user has been granted
any kind of access to.
policy sendClient (SELECT *
FROM message, acl, user
WHERE acl.Forum = message.Forum
AND acl.User = user.Id
AND known (user.Pass) )
Posting a new message requires access at level 2 or
higher.
policy mayInsert (SELECT «x
FROM message AS New, user, acl
WHERE New.User = user.Id
AND New.Forum = acl.Forum
AND user.Id = acl.User
AND known (user.Pass)
AND acl.Level >= 2
AND known (New. Subject)
AND known (New. Body) )
Regular users may not delete messages from forums.
This right is only granted to admins, who have access
level 3 or higher. The following policy formalizes the
deletion rule.
policy mayDelete (SELECT *
FROM message AS Old, user, acl
WHERE Old.Forum = acl.Forum
AND user.Id = acl.User
AND known (user.Pass)
AND acl.Level >= 3)
Our implementation allows forums to be marked as
public, in which case any visitor may read their contents.
There is also another ACL table which grants users ad-
min access to all forums. Additional policies allow in-
formation flows and updates based on these rules.
The UrFlow policy language supports access control
techniques besides user accounts with passwords. For
example, we have implemented a simple Web-based poll
system without user accounts. Anyone may create a new
poll; at that time, the creator learns a secret code that
grants admin rights to the poll. That code allows him to
add poll questions. After adding all of the questions, the
poll creator may mark the poll as live. After that time,
no further changes to the poll are allowed, and the poll
is added to a list on the application’s front page. Anyone
may vote in a live poll, but no one may vote on a poll that
is not yet live. After submitting his votes, a user receives
a code that allows him to view the results of the poll.
Results should never be released without first checking
that the user has provided a code that matches the poll
admin code or a code associated with a vote that has been
cast.
The policy below controls the conditions under which
a new question may be added to a poll. In particular,
the question must be linked to a valid poll, the user must
know the admin code for the poll, and the poll must not
be live yet.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) —- 109
110
policy mayInsert (SELECT *
FROM question AS New, poll
WHERE New.Poll = poll.Id
AND known (poll.Code)
AND NOT poll.Live
AND known (New. Text) )
Anyone with a poll’s admin code may update the poll
only to mark it as live. This policy expresses that re-
quirement with equality assertions between old and new
values of every column besides Live.
policy mayUpdate (SELECT *«
FROM poll AS New, poll AS Old
WHERE New.Id = Old.Id
AND New.Nam = Old.Nam
AND New.Code = Old.Code
AND New.Live
AND known (Old.Code) )
We allow release of information about answers to a
poll, whenever the user proves he already voted in that
poll by providing a code associated with an appropriate
answer set.
policy sendClient (SELECT ~«
FROM answer, answers AS Other,
answers AS Self
WHERE answer.Answers = Other.Id
AND Other.Poll = Self.Poll
AND known (Self.Code) )
We believe that this specification approach is very
general, while being much more accessible to the av-
erage developer than most specification languages are.
To investigate the potential for static analysis based on
these specifications, we implemented the UrFlow pro-
totype, which handles a restricted subset of all SQL
queries. In particular, in both policies and programs,
we only process queries containing just SELECT, FROM,
and WHERE clauses, where the FROM clauses must be
simple comma-separated lists of tables. We also have
not implemented any analysis optimizations like proce-
dure summaries [19], and the analysis only succeeds at
understanding loops and recursion following a few sim-
ple patterns.
Perhaps surprisingly, this is enough to enable sound
checking of a variety of paradigmatic Web applications.
We will now describe the analysis and then argue for its
effectiveness with statistics about a set of representative
applications that it has validated.
4 An Outline of the Analysis
Sound program checking requires considering all possi-
ble paths of execution. Since most any non-trivial Web
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
application can effectively follow infinitely many paths,
we must apply some abstraction. In implementing Ur-
Flow, we adopted the strategy associated with tools like
ESC [10], the Extended Static Checker family.
While concrete program evaluation involves program
states consisting of variable values, memory states, and
so on, the kind of symbolic evaluation that we apply
involves program states consisting of formulas of first-
order logic. Such a formula can be thought of as describ-
ing concrete states, so that each abstract state may stand
for infinitely many concrete states. Every basic program
operation can be modeled as a predicate transformer.
Some operations may not always be safe. In the classical
setting, this may be an array dereference, where the in-
dex might be out of bounds. In our case, possibly-unsafe
operations include write commands and database up-
dates. No matter which setting we are in, the safety of
operations is checked by associating each operation with
a logical condition that implies its safety.
This gives us the outline of a sound checking proce-
dure: Start with the abstract state “true.” Explore all pro-
gram paths, extending the abstract state as we go. Each
time we reach an operation with safety condition C’ while
in state S, ask an automated theorem prover whether
S = C. The ESC projects used the Simplify prover [8]
for this purpose. Today, the functionality provided by
Simplify is most commonly known by the name SMT,
for satisfiability modulo theories, and there is a rich base
of tools and users in the domain of static program check-
ing.
Our outline omits a critical element of the problem:
Even after abstracting program states with formulas,
there are probably still infinitely many feasible program
paths. The ESC approach requires additional program
annotations that can be used to finitize the path space. In
the design of UrFlow, we have instead taken advantage of
the control-flow simplicity of the average Web applica-
tion. Many interesting applications can be implemented
with just one kind of loop: iteration over writing some
output for every row returned by an SQL query. Such
loops effect no state changes that must be taken into ac-
count in the remainder of the program, so in a sense they
have trivially inferable “loop invariants.” Since loop iter-
ation does not accumulate side effects, it is sound to tra-
verse each loop body just once, which ensures that each
program can be broken into a finite set of finite analysis
paths.
UrFlow thus works by literal exploration of all con-
trol flow paths through a program. The next section goes
into more detail on the exploration strategy, pointing out
the theorem prover operations that will be required. The
following section presents our implementation of those
prover primitives, in an engine that extends the standard
SMT approach with a few new features.
USENIX Association
USENIX Association
5 Symbolic Evaluation
The abstract states of UrFlow are defined in terms of a
simple language of logical expressions and predicates.
We write c for constants (drawn from integer, floating
point, and string literals), 7’ for union tags, x for logi-
cal variables, X for program variables, F’ for record field
names, and F for SQL table names. The following gram-
mar describes the syntax of program states. For a token
sequence t, we write ¢ for a comma-separated list of zero
or more ts.
Expression e := cla|T(e)|{F=e}l|eF
Predicate p ::= known(e)| R(e)|e=e]|...
State S = (p,X re)
A state is a pair of a variable assignment and a set
of predicates. For a particular program point, a variable
assignment maps every in-scope program variable into a
logical expression. The predicates are expressed only in
terms of logical variables, not the program variables.
Since we inline all function calls, every execution path
to analyze begins at the entry point of some function
that has been registered to be called in response to a
particular URL pattern. The arguments to this func-
tion stand for explicit parameters and form field val-
ues, extracted from an HTTP request. Where the func-
tion arguments are named X;, we create an initial state
(known(x;), X; +4 x;), for fresh, distinct variables x;.
At many other points in path exploration, we will gen-
erate fresh logical variables, which we always assume to
be distinct from any previously-chosen variables.
For each function, we explore all paths through it.
Most program expression forms are easy to process, as
they admit direct translation into logical expressions.
The more interesting cases come from branching and
database interaction.
Our single branching construct is case expressions,
which test a value against a number of patterns, which
may bind new variables if they match. We model if
expressions as a special case of case expressions, where
the patterns to match against are t rue and false.
As an example, consider an expression like the follow-
ing:
case e of None => el | Some(X) => e2
If e is just the tag None, then we continue with eval-
uating e1. Otherwise, e is Some v for some v, and we
evaluate e2 with X set to v. To capture this with sym-
bolic evaluation, we consider both e1 and e2 as starts of
separate execution paths. For the e1 case, we extend the
state with the predicate v = None, where v is the result
of evaluating e. For the e2 case, we choose a fresh vari-
able x, add the variable mapping X +> z, and add the
predicate v = Some(r).
With case, it is easy to write code with exponentially
many control-flow paths, but where all but a few are log-
ically impossible. For instance, we can sequence several
case expressions that analyze the same program vari-
able with the same patterns. Variables are immutable, so
each case must choose the same pattern, reducing the
number of feasible paths to the number of patterns. We
want our automated theorem prover to detect the infeasi-
bility of the other paths as early as possible. Concretely,
this will happen on a path where two cases lead to as-
sertions like y = None and v = Some(zx), on a path
that assumes matching of a None pattern the first time
and a Some pattern the second time. The prover knows
that values built with different union tags are disjoint, so
it can signal a contradiction here. Whenever a contra-
diction is detected at some point on a path, we can skip
exploring the rest of that path.
A number of primitive operations send output to the
client. The simplest of these is write, which appends a
piece of HTML to the page being generated. UrFlow en-
forces that the value being sent can be constructed from
known and allowable pieces of data. Recall that allow-
able values are those that could be produced by execut-
ing sendClient policies in the current database state.
Consider this line of our earlier example program:
write (escape (r.Secret.Data));
The record r has come out of a database query. To
verify that this write conforms to the policy, we must
check that r.Secret.Data is known, allowable, or
built from such values out of record and union opera-
tions. At this point in symbolic execution, the variable
mapping will map the program variable r to some logi-
cal variable r, and our predicate set will be:
c = cookie/login, known(c), c = Some(c’), user(x1),
x1.Id = c’.ld, x,.Pass = c’.Pass,
secret(x2), %2.User = c’.Id,
r = {Secret = {Id = xr2.1d, Data = x2.Data}}
The state tells us that we know of two rows that must
exist in the database: x1; from table user and x2 from
table secret. Each of our declared confidentiality poli-
cies is phrased as a SELECT query whose FROM clause
mentions one or more tables. To check if a value may be
written, we need to consider ways of matching the pol-
icy queries with the logical state. The same table may
be mentioned multiple times in one policy or one state,
So, in general, there may be many ways to match a pol-
icy’s FROM clause with the table predicates of a state. In
UrFlow, we apply the heuristic of considering at most
one matching per policy. The analysis enumerates every
matching of policies with row variables, subject to that
constraint.
Our running example included these two policies:
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI’10) 111
112
policy sendClient (SELECT «*
FROM user
WHERE known (user.Pass) )
policy sendClient (SELECT *
FROM secret, user
WHERE secret.User = user.Id
AND known (user.Pass) )
They can be expressed in logical form, where each is
a set of predicates that, if all are true, implies the allowa-
bility of a set of values.
Predicates: user(71), known(r;.Pass)
Values: 1 1.1d,7r1.Nam, 71.Pass
Predicates: user(r ), secret(72), known(r1.Pass),
rg.User = r1.I1d
Values: 1 ,.Id,r1.Nam, 11.Pass, r2.ld,
r2.User, r2.Data
Matching a policy against a state is a two-step process.
First, we consider a mapping of the policy’s r; row vari-
ables to variables appearing in the state. For any table
predicate R(r;) appearing in the policy, we try setting r;
to x, for any R(x) appearing in the state. Once we have
found a plausible mapping for every policy row variable,
we apply that mapping to the remaining predicates in the
policy. If the theorem prover verifies that the state im-
plies every one of these predicates, then we have found a
viable policy instantiation, and we can continue match-
ing the remaining policies. We repeat the process to try
every combination of instantiating every policy at most
once.
For every set of policy instantiations, we compute the
set of expressions that those policies say are fair game
to write. Our running example has exactly one feasible
instantiation per policy: every policy variable in user
unifies with x1, and every policy variable in secret
unifies with x2. The remaining predicates are all implied
by the state. Most interestingly, we must verify that the
state implies known(x1.Pass), which follows by reason-
ing from this subset of the state predicates:
known(c), c = Some(c’), 71.Pass = c’.Pass
The reasoning goes like this: Because the union value
c is known, its contents c’ are known, too. Because the
record c’ is known, its field Pass is known. That field is
asserted equal to the value x1.Pass that we want to prove
known, so we are done. The theorem prover provides a
complete decision procedure for reasoning chains of this
kind.
Having verified correct instantiation of each policy, we
arrive at this set of allowable expressions:
x1.1d,71.Nam, 71.Pass, r2.Id, r2.User, r2.Data
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
We are trying to prove that the expression
r.Secret.Data is allowable, which requires proving
that it is equal to one of the above expressions. It turns
out that our state implies that the written value equals
X_.Data, because the state contains this predicate:
r = {Secret = {ld = x2.1d, Data = x2.Data}}
That completes the check for this write operation.
The procedure scales to handling much more compli-
cated cases, and we also apply the same procedure to any
expression used in a branching construct, such that the
result of the test influences what is written to the client.
Especially in this latter case, we need to be able to rea-
son about values that are neither known nor allowable,
but that are built from such values via record and union
operations. Our theorem prover handles the automation
of that kind of reasoning, too.
The heart of symbolic evaluation is the treatment of
database queries. Recall the form of queries, as illus-
trated by the main output loop of our example applica-
tion.
query (SELECT secret.Id, secret.Data
FROM secret
WHERE secret.User = {u})
(r acc => ...) ()
We execute an SQL query, which may contain injected
program values, and loop over the result rows. An accu-
mulator is initialized to some specified value, which here
is the dummy value (), since we execute this loop body
only for side effects. Every iteration runs the loop body
with r bound to the latest result row and acc bound to
the current accumulator. After an iteration, the accumu-
lator is replaced with the value of the . . . body expres-
sion.
Traditional verification tools require manual annota-
tion of loops with invariants, to help tame the unde-
cidability of the program analysis problem. To avoid
that cost, we designed UrFlow around some observations
about the loops that appear in practice in Web applica-
tions. Most are run solely for their side effects of writ-
ing content to the client, so that there is no need to track
state changes from iteration to iteration. Ur/Web vari-
ables are all immutable, so it is not even possible for
them to change across iterations. Side effects are re-
stricted to database tables and cookies, which tend not
to be used in the same way that variables are used in tra-
ditional imperative languages. All this implies that a sim-
ple loop traversal strategy can be very effective: traverse
each loop body only once.
Concretely, when we reach a query in a symbolic ex-
ecution path, we consider two possible sub-paths. First,
USENIX Association
USENIX Association
the query may return no results, in which case we pro-
ceed taking the initial accumulator as the final value.
More interestingly, the loop may execute one or more
times. We perform a quick linear pass over the body
. to see which cookies it might set and which tables
it might modify with SQL UPDATE or DELETE com-
mands. All references to those cookies and tables are
deleted from the symbolic state. Since all other aspects
of concrete state are immutable, this new logical state is
guaranteed to be an accurate description of the concrete
state at the beginning of any iteration of the loop. Thus,
by running the loop body with its local variables set to
fresh logical variables, we consider all possible behav-
iors of the loop. We can continue execution afterward as
if we had just executed the loop body once as normal,
non-loop code. The symbolic state at loop exit can just
as well stand for the last iteration of the loop as for any
other iteration.
At the beginning of a loop iteration, we must enrich
the logical state with predicates capturing the behavior of
the query. This is best illustrated by example. Consider
again the main loop of our example application. We ex-
ecute its loop body with variable r set to r and acc set
to some arbitrary value (since the accumulator is not ref-
erenced in the body). Assume that program variable u is
mapped to logical variable u. We add these predicates to
the logical state:
secret(x2), %2.User = wu,
r = {Secret = {Id = x2.1d, Data = x2.Data}}
Queries with joins just add more table predicates, as
we have seen in the modeling of queries as policies.
Larger WHERE conditions add additional non-table pred-
icates. A SELECT clause determines which fields to
project from the tables, in building the record expression
to equate with r.
This basic algorithm works for most of the queries that
we support. In general, UrFlow does not yet support SQL
grouping or aggregation. We include one special case for
queries selecting just the aggregate function COUNT (x).
Here, we consider that the loop body always iterates ex-
actly once. Either the query result is 0, and we do not
enrich the state with any new table information; or the
result is greater than 0, and we assert that there exists
some set of rows matching the conditions of the query.
To check database updates, we use a hybrid of the
query and write checking. Any modification must match
with an update policy, using the same matching proce-
dure as for writes, but without the need to check al-
lowability of a value. After an UPDATE or DELETE,
we delete any state predicates mentioning the affected
tables.
UrFlow also has basic support for simple recursive
functions. Calls to recursive functions are effectively in-
Figure 1: E-graph for the state from the write example
lined like regular function calls, with further self-calls
skipped. To make this omission sound, we analyze each
recursive function to find all effects it might have on the
database and cookies, and every self-call is treated as a
nondeterministic modification of those parts of the state,
followed by generation of an unknown return value. Fur-
ther analysis allows us to abstract the initial state so that
it can stand for any set of arguments that might be used at
any recursion depth, such that we only preserve state in-
formation that can be shown not to vary across calls. As
a result, just like for query loops, a single pass over the
function body suffices to consider all possible behaviors.
We want to emphasize some useful consequences of
the way that our analysis handles SQL. First, unlike in
some related work [14], despite the fact that our poli-
cies are themselves SQL queries, the analysis does not
require that program code use exactly those queries. Se-
mantic modeling of queries makes it possible for one
policy query to justify infinitely many possible program
queries. Second, the soundness of our analysis depends
on knowledge of the database schema, but not knowl-
edge of database contents. Schema changes can invali-
date analysis results by, for example, redefining data in-
tegrity constraints that the theorem-prover might have re-
lied on. However, arbitrary changes to the database rows,
by arbitrary programs with no relation to UrFlow, cannot
invalidate past analysis results.
6 The Theorem Prover
The last section highlighted the key theorem-prover op-
erations that symbolic evaluation depends on. We can
summarize them like this:
e Assert a predicate p. If p contradicts the predicates
already asserted, raise an exception indicating so.
e Check if a predicate is implied by those already as-
serted.
e Determine if a logical expression can be constructed
from members of a set of allowable expressions.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) —:113
114
The first two points are supported by the classic model
of first-order logic theorem-proving that is embodied in
tools like Simplify [8]. The third point is new and not di-
rectly supported by usual prover interfaces, but the usual
implementation techniques can support it very directly.
Provers like Simplify are based on the Nelson-Oppen
architecture. We do not use many of the elements of that
architecture, since our prototype implementation omits
features like reasoning about arithmetic. Instead, we just
adopt the key data structure, the E-graph. An E-graph
is a directed graph representation of the possible worlds
that are consistent with a set of predicates. Nodes stand
for objects, and, for function symbol f, an edge labeled
with f goes from node wu to node v if, in any compatible
world, the object associated with v equals the result of
applying f to the object associated with u. A node is
labeled with logical variables and constants to indicate
that any compatible world must assign this node to an
object equal to those variables and constants.
In UrFlow, we only use two kinds of function sym-
bols: union tags and record field names. For tag 7’, there
is a T-labeled edge from u to v if v must be u tagged
with T (i.e, “vu = T(u)”). For field name F’, there is
an F’-labeled edge from u to v if u is a record whose Ff’
component equals v. For each node that came from a lit-
eral record expression, we mark that node as complete,
in the sense that the field edges coming out of it provide
a complete description of the available fields. An exam-
ple of an incomplete record node is one representing a
row selected in an SQL query; the state will only men-
tion those columns relevant to the query, and it would be
unsound to treat this row as if it had no further columns.
Figure 1 shows an E-graph representing the
logical state given earlier for checking the code
write (escape (r.Secret.Data)). Nodes are
boxes when the state implies that they are known; other
nodes may not be known. Complete record nodes are
diamonds. We abbreviate cookie/ login as C.
The basic prover algorithm understands two kinds of
predicates: e; = e2 and known(e). When either kind is
asserted, its expressions are first evaluated into nodes of
the E-graph, adding new nodes as necessary. A variable
or constant is evaluated to the node labeled with it. A
union tag application T'(e) is evaluated by following the
T edge from the node that e evaluates to, and a field pro-
jection e.F is evaluated analogously. A record expres-
sion {F, = e€1,...,/, = en} is evaluated by checking
for existing complete nodes whose F’; edges point to the
nodes to which the e;s evaluate.
When a fact e; = eg is asserted, the nodes wu; and ug
standing for e; and e2 are merged, taking the unions of
their sets of labels and incoming and outgoing edges. Al-
ternatively, this fact might trigger a contradiction. That
happens when uw; and wz are labeled with different con-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
stants or have incoming tag edges labeled with different
tags.
When a fact known(e) is asserted, and e evaluates to
u, we “change u to a box,” and we propagate this known-
ness information across edges. That propagation follows
record field edges in the forward direction only and tag
edges in either direction. The same propagation is im-
plied when merging a known node with a not-known
node for an equality assertion.
The heart of the procedure is in this handling of as-
sertion. E-graphs have nice properties which make im-
plication checking very efficient. To check if e; = eg,
we only check if e; and eg evaluate to the same node.
To check if known(e), we only check if e evaluates to a
boxed node.
One useful addition, implemented outside of the theo-
rem prover core, takes advantage of key information for
SQL tables, where, for instance, an ID column is as-
serted not to be duplicated across rows of a table, and
the SQL engine maintains this invariant with dynamic
checks. Whenever a new predicate asserts that some row
r is in table R, we check, for every pre-existing predicate
R(r’), if r and r’ agree on the values of R’s key columns.
These checks can be implemented by querying the prover
core with the appropriate equality predicates. Whenever
a matching r and r’ pair is found, we can skip adding the
new predicate R(r) to the state, instead asserting r = r’.
This enrichment of the prover is useful in analyzing ap-
plications that, for example, query a user/password table
multiple times, where correctness relies on the fact that
the query always returns the same result.
The last ingredient is checking if the value of expres-
sion e can be constructed out of the values of expressions
€1,-..,€n, using only record and union operations. To
implement the check, we evaluate each e; in turn, mark-
ing its node as allowable. Next, we evaluate e to a node
u. If wis marked as allowable, we are done. Otherwise,
if u has an incoming union tag edge from a node v, we re-
peat the procedure for v. If u is a complete record node,
we repeat the procedure for each target of a field edge
out of u, returning success only if the check is successful
for each of these new nodes. In any other case, we return
failure.
7 Discussion
We can get a sense for the breadth of UrFlow by con-
sidering how it helps with the most common Web appli-
cation security flaws. The OWASP Top 10 Web Appli-
cation Security Risks project! is a popular reference for
security-conscious Web developers. Based on analysis
'http://www.owasp.org/index.php/Category:
OWASP_Top_Ten_Project
USENIX Association
USENIX Association
of databases of real vulnerabilities, the OWASP team has
identified which classes of security flaw pose the greatest
risks. The Ur/Web compiler rules out injection (ranked
#1) and cross-site scripting (#2) vulnerabilities and par-
tially mitigates cross-site request forgery (#5) and unval-
idated redirects and forwards (#10) using techniques un-
related to UrFlow. Risk #6, security misconfiguration, is
a whole-system property that cannot really be addressed
by any single tool, and UrFlow’s lack of integrated rea-
soning about cryptography prevents it from helping to
avoid insecure cryptographic storage (#7). UrFlow can
contribute to the mitigation of the remaining risk cate-
gories.
Risk #3, broken authentication and session manage-
ment, is helped by the ability to use UrFlow policies to
specify exactly which secure tokens may be sent to which
clients. It is still possible to make mistakes in the poli-
cies, but these policies should be significantly easier to
audit than programs, with the many possible control-flow
paths of the latter. The next two risk categories, insecure
direct object references (#4) and failure to restrict URL
access (#8), are very similar, as both involve the omission
of access control checks for particular system objects.
UrFlow can enforce that appropriate checks are always
performed whenever database objects are used in par-
ticular ways. Insufficient transport layer protection (#9)
could be avoided by adding a variant of sendClient
policies which specifies values that may only be sent to
clients over SSL connections.
Comparing against the pros and cons of security
types [16], we find some interesting trade-offs. UrFlow
uses high-level knowledge of programs to provide more
sound reasoning without program annotations. Security-
typed languages generally rely on declassification tech-
niques where trust is granted to particular spans of code.
This creates a contrast between the security-typed ap-
proach, requiring trusted code but granting soundness
with respect to implicit flows; and the UrFlow approach,
which requires no trusted Ur/Web functions but ignores
some implicit flows. Security type annotations tend to be
required throughout a program, while UrFlow avoids the
need to mark up program code. However, SQL queries as
policies involve some gotchas that would be less applica-
ble to security types. For instance, it is easy to forget all
or part of a policy WHERE clause, which has the unfortu-
nate consequence of allowing behaviors by default.
The problem of implicit flow checking is a serious one
in all kinds of information flow analysis. Where Ur-
Flow checks implicit flows, the checking is not particu-
larly clever, and implicit flows caused by WHERE clauses
are ignored. Future work may be able to plug part of
this hole statically, and we suspect there will also be a
large role for dynamic monitoring systems, for detecting
brute-force password cracking attempts and other attacks
that involve many HTTP requests.
Many different logical languages have been used for
specification-writing in static verification tools. We
found SQL to be a convenient choice, because it is ex-
pressive enough to allow direct expression of interesting
policies, and declarative enough to enable effective auto-
mated reasoning. We do not mean to claim that SQL
has great expressivity or succinctness advantages over
more traditional specification languages. Rather, most
Web programmers are accustomed to SQL, which should
help in overcoming some of the social obstacles faced in
the past by attempts to get programmers to write logical
specifications.
Our implementation today only handles a subset of the
common SQL features. We omit support for outer joins.
These should be easy to model via disjunctive formulas,
covering all the possible cases of whether a row match-
ing the join condition exists in a table, though a naive
realization of this idea would probably have poor perfor-
mance consequences for the theorem-prover. Grouping
and aggregation are harder to encode in the quantifier-
free first-order logic that we are employing. We sus-
pect that most real programs can be checked with con-
servative encodings of aggregation, where we model ag-
gregate function values as unknowns. Alternatively, we
can restrict reasoning about aggregate functions to sim-
ple syntactic pattern-matching against policies. That ap-
proach also seems most practical for handling of the SQL
EXCEPT operator, which implements a kind of negative
reasoning about which rows do not exist. This is needed
to write down policies like (for a conference manage-
ment system) “reviewer A may see the reviews for paper
B only if A does not have a conflict with B.”
More advanced policies might also need to include
non-trivial program code. For instance, a custom hash-
ing or encryption scheme might be used. Here we en-
counter a common situation for static verification, where
itis always possible to expand the reach of your theorem-
prover to handle new program features. No single imple-
mentation will ever be able to handle all realistic pro-
grams, but we suspect that very good coverage will be
possible, after the incorporation of significant practical
experience with the tool.
8 Evaluation
The UrFlow prototype is implemented in about 2200
lines of Standard ML code. We have used the analy-
sis to check a number of Ur/Web applications. There
is a live demo of the applications, with links to syntax-
highlighted source code, at:
http://www. impredicative.com/ur/scdv/
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) —115
116
Application Program Policies Check
(LoC) (LoC) (sec)
Secret 138 24 0.02
Poll 196 50 0.035
User DB 84 8 -
Calendar 255 46 0.28
Forum 412 134 17.68
Gradebook 342 61 1.49
Figure 2: Lines-of-code breakdown in case studies, with
time required to check the code with UrFlow
Our case studies include Secret, a minimal applica-
tion for storing secrets that may later be retrieved via
password authentication, which was used as the model
for this paper’s first set of running examples; the Forum
and Poll applications from which Section 3’s examples
were drawn; a Gradebook application, for managing a
database of student grades in courses; and a reimplemen-
tation of the Calendar application from the paper [5] that
introduced the SIF system for combined static and dy-
namic checking of information flow in Web applications.
Calendar, Forum, and Gradebook share a common user
authentication component.
The Calendar application lets users save details of
their schedules on the Web, with controlled sharing of in-
formation. By default, no one may learn anything about
an event. The creator of an event may learn everything
about it, and the creator may add invitees who inherit
the same read privileges. The creator may also authorize
users to know only the time of an event, so that those
users see that time slot only as “busy” on the creator’s
calendar. Only event creators may modify any state re-
lated to their events.
The Gradebook application is based on a database of
courses and assignments of users to be instructors, teach-
ing assistants (TAs), or students in courses. Each student
membership record contains an optional grade. Only sys-
tem administrators may create courses and modify in-
structor lists. Instructors may set grades and control TA
assignments. A TA may view all of the state associated
with a course, but may not modify it. A student may view
his own grades, and a student in a course may only affect
that course’s part of the database by dropping the course.
Figure 2 gives the number of lines in code in each
of these components. An application’s code is sepa-
rated into the program itself and the policies. The fig-
ures here make “policy overhead” appear bigger than it
would probably be in production applications, since our
case studies include minimal code dedicated to provid-
ing fancy user interfaces. Still, these numbers compare
favorably to those for systems like SIF, where Calendar
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
requires 1779 lines of code. While we have a similar
ratio of program to annotation, our annotations are of a
different kind. 443 lines of the SIF version include an-
notations, in the form of security types [20] and explicit
downgradings. The latter involve annotations that effec-
tively say “the owner of a piece of information trusts this
span of code, so let that span release derived information
that would not otherwise be allowed.” The SIF Calendar
case study includes 17 such downgrades.
The UrFlow approach is very different. As no annota-
tions are required in programs, there is no need to accept
any part of a program as trusted. All checking is with
respect to the declarative specification provided by the
policy queries.
Our analysis detects flaws similar to those that occur
frequently in real deployed systems. For instance, we
examined reports for July 2010 in the National Vulner-
ability Database”. Among the relevant issues, we found
CVE-2009-4927, involving privilege escalation via a sur-
prising setting of a specific cookie; and CVE-2010-2685
and CVE-2009-4929, which allow administrative actions
to be taken without proper credentials, via hand-crafted
HTTP requests. UrFlow makes it easy to catch these
problems, since it is not necessary to enumerate all pos-
sible attack vectors, thanks to policies that talk directly
about underlying resources. For instance, we introduced
a bug in the Gradebook application to mimic the cookie
bug above, where we allow anyone to set any student’s
grade if a particular cookie is set to 1. The compiler com-
plains that the database update policy may be violated,
referencing the exact span of source code where the of-
fending UPDATE statement occurs. The same output ap-
pears if we simulate a forgotten access control check, in
the style of the second two issues above, by commenting
out an important i f test.
UrFlow also requires no change to the runtime behav-
ior of a program, and this baseline performance level
is greater than for most popular Web languages and
frameworks, thanks to the general-purpose and domain-
specific optimizations performed by the Ur/Web com-
piler. We present the performance of the UrFlow anal-
ysis itself in Figure 2, for runs on a Linux machine with
dual 1 GHz AMD64 processors with 2 GB of RAM. Of
our case studies, only Forum takes much longer than a
second to check. This is because Forum has a compli-
cated main function, with many security checks. Many
different actions call the main function after perform-
ing some database modification. Every such call is an-
alyzed afresh, as if the main function had been inlined.
Techniques like procedure summaries [19] should make
it possible to reduce this time significantly.
*http://nvd.nist.gov/
USENIX Association
USENIX Association
Very precise, logic-based program analyses often ex-
hibit bad scaling behavior. There is no theoretical rea-
son that UrFlow would not run into the same problems.
Many programs with exponentially many feasible paths
will indeed trigger exponential behavior in any realiza-
tion of our algorithm. Simple experiments with param-
eterized families of programs also show that our current
implementation produces exponential running time (with
small constant factors) even on some examples that can
probably be reduced to linear running time with more op-
timization. For instance, we tested programs made up of
if-trees that perform the same SQL query at each of the
tree’s exponentially-many nodes. Primary key informa-
tion implies that the if test always goes the same way,
ruling out all but two paths through the tree. Still, expo-
nential time usage results from our heuristic of consid-
ering two execution paths starting at each query, for the
cases of zero or more than zero result rows. Much future
work remains in smarter detection of redundant paths.
9 Related Work
The BAN logic [2] is a formal system for reasoning about
knowledge in distributed system protocols. The rules of
the logic model important aspects like transitive trust and
cryptography. The spi calculus [1] pursues similar goals,
introducing an explicit formalization of programs, rather
than just of the knowledge that principals have at points
throughout a protocol. Our known predicate is modeled
on notions introduced in that line of work.
Security types [20] are a technique for static checking
of information flow based on explicit data labels such
as “high security” and “low security.” The JFlow [15]
and Jif [16] systems are realistic implementations of se-
curity typing for Java. SIF [5] extends Jif for the Web
application domain. This line of work enables check-
ing of a much broader range of applications than UrFlow
can handle. By focusing on a narrow domain that nat-
urally supports declarative implementation techniques,
UrFlow is able to do sound checking without requir-
ing any program annotations. Jif-based systems require
many annotations, including explicit granting of trust
to particular spans of code. The Swift system [4] ex-
tends this approach to do automatic, secure partitioning
of Web application code across client and server, based
on information-flow constraints.
Li and Zdancewic [14] presented a system for static
checking of information-flow properties for database-
backed Web applications. Their design requires that
the application be programmed in terms of fixed sets of
query templates with holes to be filled with different val-
ues on different invocations. Every template is annotated
with security typing information for each input and out-
put. In contrast, UrFlow infers the security-relevant char-
acteristics of queries from a declarative policy. One pol-
icy may be enough to imply the sensitivity of outputs
from many different query forms. UrFlow also applies
theorem-proving technology to allow sound checking of
more programs, including those where policies vary dy-
namically based on database contents.
Asbestos [9] and HiStar [23] are operating systems
with support for dynamic enforcement of the Decentral-
ized Information Flow Control model, which specifies
which run-time flows between sensitive objects to allow.
The Flume system [13] implements similar functionality
on top of standard UNIX abstractions. All of these sys-
tems can support complex system architectures that fall
outside the specialized orientation of UrFlow. Flume has
been used to build a secured version of the MoinMoin
wiki application. This port to Flume required about 1000
lines of new code and 1000 lines of modifications, and a
performance cost between 34% and 43% was measured,
against the baseline of interpreted Python code. Our Fo-
rum case study demonstrates that UrFlow can check poli-
cies based on access control lists, which are the main
property enforced in the Flume case study.
The Resin system [22] implements a much lighter-
weight approach to Web application security. Instead of
relying on a fixed label model, Resin allows program-
mers to implement their own property checks in the lan-
guage in which the application is written. Policy code
may tag values with policy objects, and the Resin system
takes care of flowing these policies through the system
and checking them at points where the application inter-
acts with its environment. Compared to the other systems
we have mentioned, including UrFlow, Resin makes it
much easier to add security checking to existing appli-
cations written in popular scripting languages like PHP
and Python. Resin’s lightweight policy approach can
also express policies that UrFlow’s policy queries can-
not. On the other hand, once a programmer has learned
Ur/Web and used it to implement his application, UrFlow
requires little annotation and brings the standard bene-
fits of static analysis, compared to Resin and the systems
mentioned in the previous paragraph: we get once-and-
for-all security guarantees, without the possibility of the
application being aborted because a problem is detected
at run-time; and we avoid extra run-time costs, such as
the 33% CPU overhead reported for a representative PHP
application instrumented with Resin.
Much work on Web application security focuses on
injection attacks, where bugs allow untrusted user input
to be passed to run-time program interpreters. Solutions
have employed both static [12, 21] and dynamic [11, 17]
analysis. Ur/Web rules out these problems by construc-
tion, by encoding the syntax of HTML and SQL with
richly-typed objects.
Rizvi et al. [18] present a technique for fine-grained
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) —-117
118
access control over SQL queries, based on the concept
of authorization views, which are much like UrFlow’s
policy queries. The key difference is that authorization
views are phrased in terms of variables like Suser—id
that must be filled in by some out-of-band mechanism.
With UrFlow, the correctness of authentication may itself
be verified, through reasoning about the known predi-
cate. The technique of Rizvi et al. is applied dynami-
cally to individual queries, where an allowability check
against the current database must be run for each query.
In contrast, UrFlow can prove statically that an appli-
cation never uses query results inappropriately, with no
modification to run-time database operation.
The SELinks system [7] extends the Links [6] Web
programming language with support for static tracking
of labels through trusted functions that enforce custom
policies. The natural way of expressing some queries
in SELinks involves mixing customized access control
checks with code that should be compiled into SQL
queries. The SELinks compiler handles the translation
of the custom checks into stored procedures that the
database engine can run during query evaluation. Ur-
Flow follows the alternate approach of letting the pro-
grammer be explicit about the interaction of checks and
queries, such that the static analysis verifies that all this
has been done correctly. In general, SELinks provides a
type system that makes certain types of security proofs
easier, though the SELinks compiler does not carry out
those proofs itself.
10 Conclusion
We have presented UrFlow, a static program analysis
that verifies adherence of database-backed Web applica-
tions to security policies. These policies may vary by
database state, and they are expressed as SQL queries, a
convenient format for most Web programmers. UrFlow
requires no program annotations and adds no run-time
overhead. A key direction for future work is adaptation
of UrFlow to more traditional languages, where database
access is granted less of a first-class status, so that pro-
gram analysis must be run to recover some information
that UrFlow depends on.
Acknowledgements We would like to thank Stephen
Chong, Avraham Shinnar, our shepherd Nickolai Zel-
dovich, and the anonymous referees for their very helpful
suggestions about this project and its presentation here.
References
[1] ABADI, M., AND GoRDON, A. D. A calculus for cryptographic
protocols: The spi calculus. In Proc. CCS (1997).
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
[2] BuRROwS, M., ABADI, M., AND NEEDHAM, R. A logic of
authentication. ACM Trans. Comput. Syst. 8, 1 (1990), 18-36.
[3] CHLIPALA, A. Ur: Statically-typed metaprogramming with
type-level record computation. In Proc. PLDI (2010).
[4] CHONG, S., Liu, J., MYERS, A. C., QI, X., VIKRAM, K.,
ZHENG, L., AND ZHENG, X. Secure web applications via auto-
matic partitioning. In Proc. SOSP (2007).
[5] CHONG, S., VIKRAM, K., AND MYERS, A. C. SIF: Enforc-
ing confidentiality and integrity in web applications. In Proc.
USENIX Security (2007).
[6] COOPER, E., LINDLEY, S., WADLER, P., AND YALLOP, J.
Links: Web programming without tiers. In Proc. FMCO (2006).
[7] CORCORAN, B. J., SWAMY, N., AND HICKS, M. Cross-tier,
label-based security enforcement for web applications. In Proc.
SIGMOD (2009).
[8] DETLEFS, D., NELSON, G., AND SAXE, J. B. Simplify: a theo-
rem prover for program checking. J. ACM 52, 3 (2005), 365-473.
[9] EFSTATHOPOULOS, P., KROHN, M., VANDEBOGART, S.,
FREY, C., ZIEGLER, D., KOHLER, E., MAZIERES, D.,
KAASHOEK, F., AND MORRIS, R. Labels and event processes
in the Asbestos operating system. In Proc. SOSP (2005).
[10] FLANAGAN, C., LEINO, K. R. M., LILLIBRIDGE, M., NEL-
SON, G., SAXE, J. B., AND STATA, R. Extended static checking
for Java. In Proc. PLDI (2002).
[11] HALFOND, W. G. J., AND ORSO, A. AMNESIA: Analysis and
Monitoring for NEutralizing SQL-Injection Attacks. In Proc.
ASE (2005).
[12] HUANG, Y.-W., YU, F., HANG, C., TSAI, C.-H., LEE, D.-
T., AND Kuo, S.-Y. Securing web application code by static
analysis and runtime protection. In Proc. WWW ’04 (2004).
[13] KROHN, M., Yip, A., BRODSKY, M., CLIFFER, N.,
KAASHOEK, M. F., KOHLER, E., AND MORRIS, R. Informa-
tion flow control for standard OS abstractions. In Proc. SOSP
(2007).
[14] Li, P., AND ZDANCEWIC, S. Practical information-flow control
in web-based information systems. In Proc. CSFW (2005).
[15] Myers, A. C. JFlow: Practical mostly-static information flow
control. In Proc. POPL (1999).
[16] MYERS, A. C., ZHENG, L., ZDANCEWIC, S., CHONG, S., AND
NystTROM, N. Jif: Java information flow, July 2001. Software
release at http: //www.cs.cornell.edu/jif.
[17] NGUYEN-TUONG, A., GUARNIERI, S., GREENE, D., SHIRLEY,
J., AND EVANS, D. Automatically hardening web applications
using precise tainting. In Proc. IFIP International Information
Security Conference (2005).
18] RIzviI, S., MENDELZON, A., SUDARSHAN, S., AND ROY,
P. Extending query rewriting techniques for fine-grained access
control. In Proc. SIGMOD (2004).
19] SHARIR, M., AND PNUELI, A. Two approaches to interproce-
dural data flow analysis. In Program Flow Analysis: Theory and
Applications. Prentice-Hall, 1981, pp. 189-233.
20] VOLPANO, D., AND SMITH, G. A type-based approach to pro-
gram security. In Proc. International Joint Conference on the
Theory and Practice of Software Development (1997).
21] XIE, Y., AND AIKEN, A. Static detection of security vulnerabil-
ities in scripting languages. In Proc. USENIX Security (2006).
22] Yip, A., WANG, X., ZELDOVICH, N., AND KAASHOEK, M. F.
Improving application security with data flow assertions. In Proc.
SOSP (2009).
23] ZELDOVICH, N., BOYD-WICKIZER, S., KOHLER, E., AND
MAZIERES, D. Making information flow explicit in HiStar. In
Proc. OSDI (2006).
USENIX Association
USENIX Association
Accountable Virtual Machines
Andreas Haeberlen
University of Pennsylvania
Paarijaat Aditya
Abstract
In this paper, we introduce accountable virtual ma-
chines (AVMs). Like ordinary virtual machines, AVMs
can execute binary software images in a virtualized copy
of a computer system; in addition, they can record
non-repudiable information that allows auditors to sub-
sequently check whether the software behaved as in-
tended. AVMs provide strong accountability, which is
important, for instance, in distributed systems where dif-
ferent hosts and organizations do not necessarily trust
each other, or where software is hosted on third-party
operated platforms. AVMs can provide accountability
for unmodified binary images and do not require trusted
hardware. To demonstrate that AVMs are practical, we
have designed and implemented a prototype AVM mon-
itor based on VMware Workstation, and used it to detect
several existing cheats in Counterstrike, a popular online
multi-player game.
1 Introduction
An accountable virtual machine (AVM) provides users
with the capability to audit the execution of a software
system by obtaining a log of the execution, and compar-
ing it to a known-good execution. This capability is par-
ticularly useful when users rely on software and services
running on machines owned or operated by third par-
ties. Auditing works for any binary image that executes
inside the AVM and does not require that the user trust
either the hardware or the accountable virtual machine
monitor on which the image executes. Several classes of
systems exemplify scenarios where AVMs are useful:
e in a competitive system, such as an online game
or an auction, users may wish to verify that other
players do not cheat, and that the provider of the
service implements the stated rules faithfully;
© nodes in peer-to-peer and federated systems may
wish to verify that others follow the protocol and
contribute their fair share of resources;
e cloud computing customers may wish to verify that
the provider executes their code as intended.
Rodrigo Rodrigues Peter Druschel
Max Planck Institute for Software Systems (MPI-SWS)
In these scenarios, software and hardware faults, mis-
configurations, break-ins, and deliberate manipulation
can lead to an abnormal execution, which can be costly
to users and operators, and may be difficult to detect.
When such a malfunction occurs, it is difficult to estab-
lish who is responsible for the problem, and even more
challenging to produce evidence that proves a party’s
innocence or guilt. For example, in a cloud computing
environment, failures can be caused both by bugs in the
customer’s software and by faults or misconfiguration of
the provider’s platform. If the failure was the result of a
bug, the provider would like to be able to prove his own
innocence, and if the provider was at fault, the customer
would like to obtain proof of that fact.
AVMs address these problems by providing users
with the capability to detect faults, to identify the faulty
node, and to produce evidence that connects the fault
to the machine that caused it. These properties are
achieved by running systems inside a virtual machine
that 1) maintains a log with enough information to re-
produce the entire execution of the system, and that 2)
associates each outgoing message with a cryptographic
record that links that action to the log of the execution
that produced it. The log enables users to detect faults
by replaying segments of the execution using a known-
good copy of the system, and by cross-checking the ex-
ternally visible behavior of that copy with the previously
observed behavior. AVMs can provide this capability for
any black-box binary image that can be run inside a VM.
AVMs detect integrity violations of an execution
without requiring the audited machine to run hardware
or software components that are trusted by the auditor.
When such trusted components are available, AVMs can
be extended to detect some confidentiality violations as
well, such as private data leaking out of the AVM.
This paper makes three contributions: 1) it introduces
the concept of AVMs, 2) it presents the design of an
accountable virtual machine monitor (AVMM), and 3)
it demonstrates that AVMs are practical for a specific
application, namely the detection of cheating in multi-
player games. Cheat detection is an interesting example
application because it is a serious and well-understood
problem for which AVMs are effective: they can detect
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) —-119
120
a large and general class of cheats. Out of 26 existing
cheats we downloaded from the Internet, AVMs can de-
tect every single one—without prior knowledge of the
cheat’s nature or implementation.
We have built a prototype AVMM based on VMware
Workstation, and used it to detect real cheats in Coun-
terstrike, a popular multi-player game. Our evaluation
shows that the costs of accountability in this context are
moderate: the frame rate drops by 13%, from 158 fps on
bare hardware to 137 fps on our prototype, the ping time
increases by about 5 ms, and each player must store or
transmit a log that grows by about 148 MB per hour af-
ter compression. Most of this overhead is caused by log-
ging the execution; the additional cost for accountabil-
ity is comparatively small. The log can be transferred
to other players and replayed there during the game (on-
line) or after the game has finished (offline).
While our evaluation in this paper focuses on games
as an example application, AVMs are useful in other
contexts, e.g., in p2p and federated systems, or to verify
that a cloud platform is providing its services correctly
and is allocating the promised resources [18]. Our pro-
totype AVMM already supports techniques such as par-
tial audits that would be useful for such applications, but
a full evaluation is beyond the scope of this paper.
The rest of this paper is structured as follows. Sec-
tion 2 discusses related work, Section 3 explains the
AVM approach, and Section 4 presents the design of our
prototype AVMM. Sections 5 and 6 describe our imple-
mentation and report evaluation results in the context of
games. Section 7 describes other applications and pos-
sible extensions, and Section 8 concludes this paper.
2 Related work
Deterministic replay: Our prototype AVMM relies on
the ability to replay the execution of a virtual machine.
Replay techniques have been studied for more than two
decades, usually in the context of debugging, and ma-
ture solutions are available [6, 15, 16, 39]. However,
replay by itself is not sufficient to detect faults on a re-
mote machine, since the machine could record incorrect
information in such a way that the replay looks correct,
or provide inconsistent information to different auditors.
Improving the efficiency of replay is an active re-
search area. Remus [11] contributes a highly efficient
snapshotting mechanism, and many current efforts seek
to improve the efficiency of logging and replay for
multi-core systems [13, 16, 28, 29]. AVMMs can di-
rectly benefit from these innovations.
Accountability: Accountability in distributed systems
has been suggested as a means to achieve practical se-
curity [26], to create an incentive for cooperative be-
havior [14], to foster innovation and competition in the
Internet [4, 27], and even as a general design goal for
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
dependable networked systems [43]. Several prior sys-
tems provide accountability for specific applications, in-
cluding network storage services [44], peer-to-peer con-
tent distribution networks [31], and interdomain rout-
ing [2, 20]. Unlike these systems, AVMs are application
independent. PeerReview [21] provides accountability
for general distributed systems. However, PeerReview
must be closely integrated with the application, which
requires source code modifications and a detailed under-
standing of the application logic. It would be impracti-
cal to apply PeerReview to an entire VM image with
dozens of applications and without access to the source
code of each. AVMs do not have these limitations; they
can make software accountable ‘out of the box’.
Remote fault detection: GridCop [42] is a compiler-
based technique that can be used to monitor the progress
and execution of a remotely executing program by in-
specting periodic beacon packets. GridCop is designed
for a less hostile environment than AVMs: it assumes a
trusted platform and self-interested hosts. Also, Grid-
Cop does not work for unmodified binaries, and it can-
not produce evidence that would convince a third party
that a fault did or did not happen.
A trusted computing platform can be used to detect if
a node is running modified software [17, 30]. The ap-
proach requires trusted hardware, a trusted OS kernel,
and a software and hardware certification infrastructure.
Pioneer [36] can detect such modifications using only
software, but it relies on recognizing sub-millisecond
delay variations, which restricts its use to small net-
works. AVMs do not require any trusted hardware and
can be used in wide-area networks.
Cheat detection: Cheating in online games is an impor-
tant problem that affects game players and game oper-
ators alike [24]. Several cheat detection techniques are
available, such as scanning for known hacks [23, 35] or
defenses against specific forms of cheating [7, 32]. In
contrast to these, AVMs are generic; that is, they are ef-
fective against an entire class of cheats. Chambers et
al. [9] describe another technique to detect if players
lie about their game state. The system relies on a form
of tamper-evident logs; however, the log must be inte-
grated with the game, while AVMs work for unmodified
games.
3 Accountable Virtual Machines
3.1 Goals
Figure | depicts the basic scenario we are concerned
with in this paper. Alice is relying on Bob to run some
software S on a machine M, which is under Bob’s con-
trol. However, Alice cannot observe // directly, she can
only communicate with it over the network. Our goal
is to enable Alice to check whether M behaves as ex-
USENIX Association
USENIX Association
Software S
8s: &> 48 2
Alice Network Machine M_ Bob
Figure 1: Basic scenario. Alice is relying on software
S, which is running on a machine that is under Bob’s
control. Alice would like to verify that the machine is
working properly, and that Bob has not modified S.
pected, without having to trust Bob, M, or any software
running on M.
To define the behavior Alice expects M to have, we
assume that Alice has some reference implementation of
M called Mp, which runs S. We say that M is correct
iff Mp can produce the same network output as / when
it is started in the same initial state and given precisely
the same network inputs. If M is not correct, we say
that it is faulty. This can happen if differs from Mp,
or Bob has installed software other than S. Our goal is
to provide the following properties:
e Detection: If J/ is faulty, Alice can detect this.
e Evidence: When Alice detects a fault on M/, she
can obtain evidence that would convince a third
party that M is faulty, without requiring that this
party trust Alice or Bob.
We are particularly interested in solutions that work for
any software S' that can execute on M and Mp. For
example, S could be a program binary that was com-
piled by someone other than Alice, it could be a complex
application whose details neither Alice nor Bob under-
stand, or it could be an entire operating system image
running a commodity OS like Linux or Windows.
In the rest of this paper, we will omit explicit refer-
ences to S when it is clear from the context which soft-
ware MM is expected to run.
3.2 Approach
To detect faults on M/, Alice must be able to answer
two questions: 1) which exact sequence of network mes-
sages did M send and receive, and 2) is there a correct
execution of I/p that is consistent with this sequence of
messages? Answering the former is not trivial because
a faulty 14 —or a malicious Bob—could try to falsify
the answer. Answering the latter is difficult because the
number of possible executions for any nontrivial soft-
ware is large.
Alice can solve this problem by combining two seem-
ingly unrelated technologies: tamper-evident logs and
virtual machines. A tamper-evident log [21] requires
each node to record all the messages it has sent or re-
ceived. Whenever a message is transmitted, the sender
and the receiver must prove to each other that they have
added the message to their logs, and they must commit
to the contents of their logs by exchanging an authenti-
cator — essentially, a signed hash of the log. The authen-
ticators provide nonrepudiation, and they can be used to
detect when a node tampers with its log, e.g., by forging,
omitting, or modifying messages, or by forking the log.
Once Alice has determined that /’s message log is
genuine, she must either find a correct execution of M/ Rp
that matches this log, or establish that there isn’t one. To
help Alice with this task, 17 can be required to record
additional information about nondeterministic events in
the execution of S. Given this information, Alice can
use deterministic replay [8, 15] to find the correct exe-
cution on Mp, provided that one exists.
Recording the relevant nondeterministic events seems
difficult at first because we have assumed that neither
Alice nor Bob have the expertise to make modifications
to S; however, Bob can avoid this by using a virtual
machine monitor (VMM) to monitor the execution of
and to capture inputs and nondeterministic events in a
generic, application-independent way.
3.3. AVM monitors
The above building blocks can be combined to con-
struct an accountable virtual machine monitor (AVMM),
which implements AVMs. Alice and Bob can use an
AVMM to achieve the goals from Section 3.1 as follows:
1. Bob installs an AVMM on his computer and runs
the software S inside an AVM. (From this point
forward, M refers to the entire stack consisting
of Bob’s computer, the AVMM running on Bob’s
computer, and Alice’s virtual machine image S,
which runs on the AVMM.)
2. The AVMM maintains a tamper-evident log of the
messages M sends or receives, and it also records
any nondeterministic events that affect S.
3. When Alice receives a message from M, she de-
taches the authenticator and saves it for later.
4. Alice periodically audits M/ as follows: she asks
the AVMM for its log, verifies it against the au-
thenticators she has collected, and then uses deter-
ministic replay to check the log for faults.
5. If replay fails or the log cannot be verified against
one of the authenticators, Alice can give Mp, S,
the log, and the authenticators to a third party, who
can repeat Alice’s checks and independently verify
that a fault has occurred.
This generic methodology meets our previously stated
goals: Alice can detect faults on M, she can obtain evi-
dence, and a third party can check the evidence without
having to trust either Alice or Bob.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI’10) = 121
122
(a) Symmetric multi-party scenario (online game)
a 8
eainee we
Bob
(b) Asymmetric multi-party scenario (web service)
Figure 2: Multi-party scenarios. The scenario on the left represents a multi-player game; each player is running the
game client on his local machine and wants to know whether any other players are cheating. The scenario on the right
represents a hosted web service: Alice’s software is running on Bob’s machine, but the software typically interacts
with users other than Alice, such as Alice’s customers.
3.4 Does the AVMM have to be trusted?
A perhaps surprising consequence of this approach is
that the AVMM does not have to be trusted by Alice.
Suppose Bob is malicious and secretly tampers with Al-
ice’s software and/or the AVMM, causing M/ to become
faulty. Bob cannot prevent Alice from detecting this: if
he tampers with M’s log, Alice can tell because the log
will not match the authenticators; if he does not, Alice
obtains the exact sequence of observable messages
has sent and received, and since by our definition of a
fault there is no correct execution of / pz that is consis-
tent with this sequence, deterministic replay inevitably
fails, no matter what the AVMM recorded.
3.5 Must Alice check the entire log?
For many applications, including the game we consider
in this paper, it is perfectly feasible for Alice to audit
M’s entire log. However, for long-running, compute-
intensive applications, Alice may want to save time by
doing spot checks on a few log segments instead. The
AVMM can enable her to do this by periodically taking
a snapshot of the AVM’s state. Thus, Alice can inde-
pendently inspect any segment that begins and ends at a
snapshot.
Spot checking sacrifices the completeness of fault de-
tection for efficiency. If Alice chooses to do spot checks,
she can only detect faults that manifest as incorrect state
transitions in the segments she inspects. An incorrect
state transition in an unchecked segment, on the other
hand, could permanently modify M/’s state in a way
that is not detectable by checking subsequent segments.
Therefore, Alice must be careful when choosing an ap-
propriate policy.
Alice could inspect a random sample of segments plus
any segments in which a fault could most likely have a
long-term effect on the AVM’s state (e.g., during initial-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
ization, authentication, key generation). Or, she could
inspect segments when she observes suspicious results,
starting with the most recent segment and working back-
wards in reverse chronological order. Spot-checking is
most effective in applications where the faults of interest
likely occur repeatedly and a single instance causes lim-
ited harm, where the application state is frequently re-
initialized (preventing long-term effects of a single un-
detected fault on the state), or where the threat of prob-
abilistic detection is strong enough to deter attackers.
3.6 Do AVMs work with multiple parties?
So far, we have focused on a simple two-party scenario;
however, AVMs can be used in more complex scenar-
ios. Figure 2 shows two examples. In the scenario on
the left, the players in an online multi-player game are
using AVMs to detect whether someone is cheating. Un-
like the basic scenario in Figure 1, this scenario is sym-
metric in the sense that each player is both running soft-
ware and is interested in the correctness of the software
on all the other machines. Thus, the roles of auditor
and auditee can be played by different parties at differ-
ent times. The scenario on the right represents a hosted
web service: the software is controlled and audited by
Alice, but the software typically interacts with parties
other than Alice, such as Alice’s customers.
For clarity, we will explain our system mostly in
terms of the simple two-party scenario in Figure 1. In
Section 4.6, we will describe differences for the multi-
party case.
4 AVMM design
To demonstrate that AVMs are practical, we now present
the design of a specific AVMM.
USENIX Association
USENIX Association
4.1 Assumptions
Our design relies on the following assumptions:
1. All transmitted messages are eventually received,
if retransmitted sufficiently often.
2. All parties (machines and users) have access to a
hash function that is pre-image resistant, second
pre-image resistant, and collision resistant.
3. Each party has a certified keypair, which can be
used to sign messages. Neither signatures nor cer-
tificates can be forged.
4. If a user needs to audit the log of a machine, the
user has access to a reference copy of the VM im-
age that the machine is expected to use.
The first two are common assumptions made about prac-
tical distributed systems. In particular, the first assump-
tion is required for liveness, otherwise it could be im-
possible to ever complete an audit. The third assump-
tion could be satisfied by providing each machine with a
keypair that is signed by the administrator; it is needed
to prevent faulty machines from creating fake identities.
The fourth assumption is required so that the auditor
knows which behaviors are correct.
4.2 Roadmap
Our design instantiates each of the building blocks we
have described in Section 3.2: a VMM, a tamper-evident
log, and an auditing mechanism. Here, we give a brief
overview; the rest of this section describes each building
block in more detail.
For the tamper-evident log (Section 4.3), we adapt a
technique from PeerReview [21], which already comes
with a proof of correctness [22]. We extend this log to
also include the VMM’s execution trace.
The VMM we use in this design (Section 4.4) virtual-
izes a standard commodity PC. This platform is attrac-
tive because of the vast amount of existing software that
can run on it; however, for historical reasons, it is harder
to virtualize than a more modern platform such as Java
or .NET. In addition, interactions between the software
and the virtual ‘hardware’ are much more frequent than,
e.g., in Java, resulting in a potentially higher overhead.
For auditing (Section 4.5), we provide a tool that au-
thenticates the log, then checks it for tampering, and
finally uses deterministic replay to determine whether
the contents of the log correspond to a correct execu-
tion of Mp. If the tool finds any discrepancy between
the events in the log and the events that occur during
replay, this indicates a fault. Note that, while events
such as thread scheduling may appear nondeterminis-
tic to an application, they are in fact deterministic from
the VMM’s perspective. Therefore, as long as all ex-
ternal events (e.g. timer interrupts) are recorded in the
log, even race conditions are reproduced exactly during
replay and cannot result in false positives. !
4.3 Tamper-evident log
The tamper-evident log is structured as a hash chain;
each log entry is of the form e; := (5;,t;, c;, hi), where
8; 18 a monotonically increasing sequence number, t;
a type, and c; data of the specified type. h; is a hash
value that must be linked to all the previous entries in
the log, and yet efficient to create. Hence, we compute
itash; = A(hj-1 || Sj || L; || H(c;)) where ho := 0, H
is a hash function, and || stands for concatenation.
To detect when Bob’s machine M forges incoming
messages, Alice signs each of her messages with her
own private key. The AVMM logs the signatures to-
gether with the messages, so that they can be verified
during an audit, but it removes them before passing the
messages on to the AVM. Thus, this process is transpar-
ent to the software running inside the AVM.
To ensure nonrepudiation, the AVMM attaches an
authenticator to each outgoing message m. The au-
thenticator for an entry e; is a; := (5;, hi, 7(5; || hi)),
where the o(-) operator denotes a cryptographic sig-
nature with the machine’s private key. M also in-
cludes hj_1, so that Alice can recalculate hj; =
H(hi_1 || 8; || SEND || H(m)) and thus verify that the
entry e; is in fact SEND(m).
To detect when / drops incoming or outgoing mes-
sages, both Alice and the AVMM send an acknowledg-
ment for each message m they receive. Analogous to the
above, /’s authenticator in the acknowledgment con-
tains enough information for the recipient to verify that
the corresponding entry is RECV(m). Alice’s own ac-
knowledgment contains just a signed hash of the cor-
responding message, which the AVMM logs for Alice.
When an acknowledgment is not received, the original
message is retransmitted a few times. If Alice stops re-
ceiving messages from JV altogether, she can only sus-
pect that M has failed.
When Alice wants to audit M, she retrieves a pair of
authenticators (e.g., the ones with the lowest and highest
sequence numbers) and challenges M to produce the log
segment that connects them. She then verifies that the
hash chain is intact. Because the hash function is second
pre-image resistant, it is computationally infeasible to
modify the log without breaking the hash chain. Thus,
if M has reordered or tampered with a log entry in that
segment, or if it has forked its log, /7’s hash chain will
no longer match its previously issued authenticators, and
Alice can detect this using this check.
'Ensuring deterministic replay on multiprocessor machines is
more difficult. We will discuss this in Section 7.4.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) = 123
124
4.4 Virtual machine monitor
In addition to recording all incoming and outgoing mes-
sages to the tamper-evident log, the AVMM logs enough
information about the execution of the software to en-
able deterministic replay.
Recording nondeterministic inputs: The AVMM must
record all of the AVM’s nondeterministic inputs [8]. If
an input is asynchronous, the precise timing within the
execution must be recorded, so that the input can be re-
injected at the exact same point during replay. Hardware
interrupts, for example, fall into this category. Note that
wall-clock time is not sufficiently precise to describe the
timing of such inputs, since the instruction timing can
vary on most modern CPUs. Instead, the AVMM uses a
combination of instruction pointer, branch counter, and,
where necessary, additional registers [15].
Not all inputs are nondeterministic. For example, the
values returned by accesses to the AVM’s virtual hard-
disk need not be recorded. Alice knows the system im-
age that the machine is expected to use, and can thus
reconstruct the correct inputs during replay. Also many
inputs such as software interrupts are synchronous, that
is, they are explicitly requested by the AVM. Here, the
timing need not be recorded because the requests will be
issued again during replay.
Detecting inconsistencies: The tamper-evident log now
contains two parallel streams of information: Message
exchanges and nondeterministic inputs. Incoming mes-
sages appear in both streams: first as messages, and
then, as the AVM reads the bytes in the message, as a
sequence of inputs. If Bob is malicious, he might try to
exploit this by forging messages or by dropping or mod-
ifying a message that was received on M before it is
injected into the AVM. To detect this, the AVMM cross-
references messages and inputs in such a way that any
discrepancies can easily be detected during replay.
Snapshots: To enable spot checking and incremental
audits (Section 3.5), the AVMM periodically takes a
snapshot of the AVM’s current state. To save space,
snapshots are incremental, that is, they only contain
the state that has changed since the last snapshot. The
AVMM also maintains a hash tree over the state; af-
ter each snapshot, it updates the tree and then records
the top-level value in the log. When Alice audits a log
segment, she can either download an entire snapshot or
incrementally request the parts of the state that are ac-
cessed during replay. In either case, she can use the hash
tree to authenticate the state she has downloaded.
Taking frequent snapshots enables Alice to perform
fine-grain audits, but it also increases the overhead.
However, snapshotting techniques have become very ef-
ficient; recent work on VM replication has shown that
incremental snapshots can be taken up to 40 times per
second [11] and with only brief interruptions of the VM,
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
on the order of a few milliseconds. Accountability re-
quires only infrequent snapshots (once every few min-
utes or hours), so the overhead should be low.
4.5 Auditing and replay
When Alice wants to audit a machine M, she performs
the following three steps. First, Alice obtains a segment
of M’s log and the authenticators that 7 produced dur-
ing the execution, so that the log’s integrity can be ver-
ified. Second, she downloads a snapshot of the AVM
at the beginning of the segment. Finally, she replays
the entire segment, starting from the snapshot, to check
whether the events in the log correspond to a correct ex-
ecution of the reference software.
Verifying the log: When Alice wants to audit a log
segment e;...e;, She retrieves the authenticators she
has received from M with sequence numbers in [s;, 5].
Next, Alice downloads the corresponding log segment
L;; from M, starting with the most recent snapshot be-
fore e; and ending at e;; then she verifies the segment
against the authenticators to check for tampering. If this
step succeeds, Alice is convinced that the log segment
is genuine; thus, she is left with having to establish that
the execution described by the segment is correct.
If M is faulty, Alice may not be able to download
Lj; at all, or M could return a corrupted log segment
that causes verification to fail. In either case, Alice can
use the most recent authenticator a; as evidence to con-
vince a third party of the fault. Since the authenticator
is signed, the third party can use a; to verify that log
entries with sequence numbers up to s; must exist; then
it can repeat Alice’s audit. If no reply is obtained, Alice
will suspect Bob.
Verifying the snapshot: Next, Alice must obtain a
snapshot of the AVM’s state at the beginning of the log
segment L;;. If Alice is auditing the entire execution,
she can simply use the original software image S. Oth-
erwise she downloads a snapshot from M and recom-
putes the hash tree to authenticate it against the hash
value in L;;.
Verifying the execution: For the final step, Alice needs
three inputs: The log segment L;;, the VM snapshot,
and the public keys of MM and any users who communi-
cated with M/. The audit tool performs two checks on
Lj, a syntactic check and a semantic check. The syn-
tactic check determines whether the log itself is well-
formed, whereas the semantic check determines whether
the information in the log corresponds to a correct exe-
cution of Mp.
For the syntactic check, the audit tool checks whether
all log entries have the proper format, verifies the cryp-
tographic signatures in each message and acknowledg-
ment, checks whether each message was acknowledged,
and checks whether the sequence of sent and received
USENIX Association
USENIX Association
messages corresponds to the sequence of messages that
enter and exit the AVM. If any of these tests fail, the tool
reports a fault.
For the semantic check, the tool locally instantiates a
virtual machine that implements Mp, and it initializes
the machine with the snapshot, if any, or S. Next, it
reads L;; from beginning to end, replaying the inputs,
checking the outputs against the outputs in L;;, and ver-
ifying any snapshot hashes in L;; against snapshots of
the replayed execution (to be sure that the snapshot at
the end of L;,; is also correct). If there is any discrep-
ancy whatsoever (for example, if the virtual machine
produces outputs that are not in the log, or if it requests
the synchronous inputs in a different order), replay ter-
minates and reports a fault. In this case, Alice can use
L,,; and the authenticators as evidence to convince Bob,
or any other interested party, that / is faulty.
If the log segment L;; passes all of the above checks,
the tool reports success and then terminates. Auditing
can be performed offline (after the execution of a given
log segment is finished) or online (while the execution
is in progress).
4.6 Miulti-party scenario
So far, we have described the AVMM in terms of the
simple two-party scenario. A multi-party scenario re-
quires three changes. First, when some user wants to
audit a machine J/, he needs to collect authenticators
from other users that may have communicated with M.
In the gaming scenario in Figure 2(a), Alice could down-
load authenticators from Charlie before auditing Bob. In
the web-service scenario in Figure 2(b), the users could
forward any authenticators they receive to Alice.
Second, with more than two parties, network prob-
lems could make the same node appear unresponsive to
some nodes and alive to others. Bob could exploit this,
for instance, to avoid responding to Alice’s request for
an incriminating log segment, while continuing to work
with other nodes. To prevent this type of attack, Al-
ice forwards the message that M does not answer as a
challenge for M to the other nodes. All nodes stop com-
municating with M until it responds to the challenge. If
M is correct but there is a network problem between MZ
and Alice, or M was temporarily unresponsive, it can
answer the challenge and its response is forwarded to
Alice.
Third, when one user obtains evidence of a fault, he
may need to distribute that evidence to other interested
parties. For example, in the gaming scenario, if Alice
detects that Bob is cheating, she can send the evidence
to Charlie, who can verify it independently; then both
can decide never to play with Bob again.
4.7 Guarantees
Given our assumptions from Section 4.1 and the fault
definition from Section 3.1, the AVMM offers the fol-
lowing two guarantees:
e Completeness: If the machine M is faulty, a full
audit of M will report a fault and produce evidence
against M that can be verified by a third party.
e Accuracy: If the machine M is not faulty, no audit
of M will report a fault, and there cannot exist any
valid evidence against M.
If Alice performs spot checks on a number of log seg-
ments s;,..., 5% rather than a full audit, accuracy still
holds. However, if / is faulty, her audit will only re-
port the fault and produce evidence if there exists at least
one log segment s; in which the fault manifests. These
guarantees are independent of the software S, and they
hold for any fault that manifests as a deviation from M pr,
even if Alice, Bob, and/or other users are malicious. A
proof of these properties is presented in a separate tech-
nical report [19].
Since our design is based on the tamper-evident log
from PeerReview [21], the resulting AVMM inherits a
powerful property from PeerReview: in a distributed
system with multiple nodes, it is possible to audit the
execution of the entire system by auditing each node in-
dividually. For more details, please refer to [21].
4.8 Limitations
We note two limitations implied by the AVMM’s guar-
antees. First, AVMs cannot detect bugs or vulnerabili-
ties in the software S', because the expected behavior of
M is defined by Mp and thus S. If S has a bug and the
bug is exercised during an execution, an audit will suc-
ceed. For instance, if S allows unauthorized software
modifications, Bob could use this feature to change or
replace S. Alice must therefore make sure that S' does
not have vulnerabilities that Bob could exploit.
Second, any behavior that can be achieved by pro-
viding appropriate inputs to M/p is considered correct.
When such inputs come from sources other than the net-
work, they cannot be verified during an audit. In some
applications, Bob may be able to exploit this fact by
recording local (non-network) inputs in the log that elicit
some behavior in Mp he desires.
5 Application: Cheat detection in games
AVMs and AVMMs are application-independent, but for
our evaluation, we focus on one specific application,
namely cheat detection. We begin by characterizing the
class of cheats that AVMs can detect, and we discuss
how AVMs compare to the anti-cheat systems that are
in use today.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) =: 125
126
5.1 How are cheats detected today?
Today, many online games use anti-cheating systems
like PunkBuster [35], the Warden [23] or Valve Anti-
Cheat (VAC) [38]. These systems work by scanning the
user’s machine for known cheats [23, 24, 35]; some al-
low the game admins to request screenshots or to per-
form memory scans. In addition to privacy concerns,
this approach has led to an arms race between cheaters
and game maintainers, in which the former constantly
release new cheats or variations of existing ones, and the
latter must struggle to keep their databases up to date.
5.2 How can AVMs be used with games?
Recall that AVMs run entire VM images rather than in-
dividual programs. Hence, the players first need to agree
on a VM image that they will use. For example, one of
them could install an operating system and the game it-
self in a VM, create a snapshot of the VM, and then
distribute the snapshot to the other players. Each player
then initializes his AVM with the agreed-upon snapshot
and plays while recording a log. If a player wishes to
reassure himself that other players have not cheated, he
can request their logs (during or after the game), check
them for tampering, and replay them using his own,
trusted copy of the agreed-upon VM image.
Since many cheats involve installing additional pro-
grams or modifying existing ones, it is important to dis-
able software installation in the snapshot that is used
during the game, e.g., by revoking the necessary privi-
leges from all accounts that are accessible to the players.
Otherwise, downloading and installing a cheat would
simply be re-executed during replay without causing any
discrepancies. However, note that this restriction is only
required during the game; it does not prevent the main-
tainer of the original VM image from installing upgrades
or patches.
5.3. How do players cheat in games?
Players can cheat in many different ways — a recent tax-
onomy [41] identified no less than fifteen different types
of cheats, including collusion, denial of service, timing
cheats, and social engineering. In Section 5.4, we dis-
cuss which of these cheats AVMs are effective against,
and we illustrate our discussion with three concrete ex-
amples of cheats that are used in Counterstrike. Since
the reader may not be familiar with these cheats, we de-
scribe them here first.
The first cheat is an aimbot. Its purpose to help the
cheater with target acquisition. When the aimbot is ac-
tive, the cheater only needs to point his weapon in the
approximate direction of an opponent; the aimbot then
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
automatically aims the weapon exactly at that opponent.
An aimbot is an example of a cheat that works, at least
conceptually, by feeding the game with forged inputs.
The second cheat is a wallhack. Its purpose is to al-
low the cheater to see through opaque objects, such as
walls. Wallhacks work because the game usually ren-
ders a much larger part of the scenery than is actually
visible on screen. Thus, if the textures on opaque ob-
jects are made transparent or removed entirely, e.g., by
a special graphics driver [37], the objects behind them
become visible. A wallhack is an example of a cheat
that violates secrecy; it reveals information that is avail-
able to the game but is not meant to be displayed.
The third cheat is unlimited ammunition. The vari-
ant we used identifies the memory location in the Coun-
terstrike process that holds the cheater’s current amount
of ammunition, and then periodically writes a constant
value to that location. Thus, even if the cheater con-
stantly fires his weapon, he never runs out (similar
cheats exist for other resources, e.g., unlimited health).
This cheat changes the network-visible behavior of the
cheater’s machine. It is representative of a larger class
of cheats that rely on modifying local in-memory state;
other examples include teleportation, which changes the
variable that holds the player’s current position, or un-
limited health.
5.4 Which cheats can AVMs detect?
AVMs are effective against two specific, broad classes
of cheats, namely
1. cheats that need to be installed along with the game
in some way, e.g., as loadable modules, patches, or
companion programs; and
2. cheats that make the network-visible behavior of
the cheater’s machine inconsistent with any correct
execution.
Both types of cheats cause replay to fail when the
cheater’s machine is audited. In the first case, the reason
is that replay can only succeed if the VM images used
during recording and replay produce the same sequence
of events recorded in the log. If different code is exe-
cuted or different data is read at any time, replay almost
certainly diverges soon afterward. In the second case,
replay fails by definition because there exists no correct
execution that is consistent with the network traffic the
cheater’s machine has produced.
If a cheat is in the first class but not in the second,
it may be possible to re-engineer it to avoid detection.
Common examples include cheats that violate secrecy,
such as wallhacks, and cheats that rely on forged inputs,
such as aimbots. For instance, a cheater might imple-
ment an aimbot as a separate program that runs outside
USENIX Association
USENIX Association
Total number of cheats examined
Cheats detectable with AVMs
... in this specific implementation of the cheat
... nO matter how the cheat is implemented
Cheats not detectable with AVMs
Table 1: Detectability of Counterstrike cheats from pop-
ular Counterstrike discussion forums
of the AVM and aims the player’s weapon by feeding
fake inputs to the AVM’s USB port. A particularly tech-
savvy cheater might even set up a second machine that
uses a camera to capture the game state from the first
machine’s screen and a robot arm to type commands on
the first machine’s keyboard. While such cheats are by
no means impossible, they do require substantially more
effort and expertise than a simple patch or module that
manipulates the game state directly. Thus, AVMs raise
the bar significantly for such cheats.
In contrast, cheats in the second class can be detected
by AVMs in any implementation. Examples of such
cheats include unlimited ammunition, unlimited health,
or teleportation. For instance, if a player has & rounds
of ammunition and uses a cheat of any type to fire more
than & shots, replay inevitably fails because there is no
correct execution of the game software in which a player
can fire after having run out of ammunition. AVMs are
effective against any current or future cheats that fall
into this category.
We hypothesize that the first class includes almost
all cheats that are in use today. To test this hypothe-
sis, we downloaded and examined 26 real Counterstrike
cheats from popular discussion forums on the Internet
(Table 1). We found that every single one of them had to
be installed in the game AVM to be effective, and would
therefore be detected. We also found that at least 4 of
the 26 cheats additionally belonged to the second class
and could therefore be detected not only in their current
form, but also in any future implementation.
5.5 Summary
Even though we did not specifically design AVMs for
cheat detection, they do offer three important advan-
tages over current anti-cheating solutions like VAC or
PunkBuster. First, they protect players’ privacy by sep-
arating auditable computation (the game in the AVM)
from non-auditable computation (e.g., browser or bank-
ing software running outside the AVM). Second, they
are effective against virtually all current cheats, includ-
ing novel, rare, or unknown cheats. Third, they are guar-
anteed to detect all possible cheats of a certain type, no
matter how they are implemented.
6 Evaluation
In this section, we describe our AVMM prototype, and
we report how we used it to detect cheating in Coun-
terstrike, a popular multi-player game. Our goal is to
answer the following three questions:
1. Does the AVMM work with state-of-the-art games?
2. Are AVMs effective against real cheats?
3. Is the overhead low enough to be practical?
6.1 Prototype implementation
Our prototype AVMM implementation is based on
VMware Workstation 6.5.1, a state-of-the-art virtual
machine monitor whose source code we obtained
through VMware’s Academic Program. VMware Work-
station supports a wide range of guest operating sys-
tems, including Linux and Microsoft Windows, and its
VMM already supports many features that are useful
for AVMs, such as deterministic replay and incremen-
tal snapshots. We extended the VMM to record ex-
tra information about incoming and outgoing network
packets, and we added support for tamper-evident log-
ging, for which we adapted code from PeerReview [21].
Since VMware Workstation only supports uniprocessor
replay, our prototype is limited to AVMs with a single
virtual core (see Section 7.4 for a discussion of multi-
processor replay). However, most of the logging func-
tionality is implemented in a separate daemon process
that communicates with the VMM through kernel-level
pipes, so the AVMM can take advantage of multi-core
CPUs by using one of the cores for logging, crypto-
graphic operations and auditing, while running AVMs
on the other cores at full speed.
Our audit tool implements a two-step process: Play-
ers first perform the syntactic check using a separate tool
and then run the semantic check by replaying the log ina
local AVM, using a copy of the VM image they trust. If
at least one of the two stages fails, they can give the log
and the authenticators as evidence to fellow players—
or, indeed, any third party. All steps are deterministic,
so the other party will obtain the same result.
6.2 Experimental setup
For our evaluation, we used the AVMM prototype to de-
tect cheating in Counterstrike. There are two reasons for
this choice. First, Counterstrike is played in a variety of
online leagues, as well as in worldwide championships
such as the World Cyber Games, which makes cheat-
ing a matter of serious concern. Second, there is a large
and diverse ecosystem of readily available Counterstrike
cheats, which we can use for our experiments.
Our experiments are designed to model a Counter-
strike game as it would be played at a competition or
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) 127
128
350
—s— AVMM log
300 —e— Equivalent VMware log
250
200
150
Log size (MB)
100
50
j- [
0 5 10 15 20 25 30 35
Time (minutes)
Figure 3: Growth of the AVMM log, and an equivalent
VMware log, while playing Counterstrike.
at a LAN party. We used three Dell Precision T1500
workstations, one for each player, with 8 GB of mem-
ory and 2.8 GHz Intel Core i7 860 CPUs. Each CPU
has four cores and two hyperthreads per core. The ma-
chines were connected to the same switch via | Gbps
Ethernet links, and they were running Linux 2.6.32 (De-
bian 5.0.4) as the host operating system. On each ma-
chine, we installed an AVMM binary that was based on
a VMware Workstation 6.5.1 release build. Each player
had access to an ‘official? VM snapshot, which con-
tained Windows XP SP3 as the guest operating system,
as well as Counterstrike 1.6 at patch version 1.1.2.5.
Sound and voice were disabled in the game and in
VMware. As discussed in Section 5.2, we configured
the snapshot to disallow software installation. In the
snapshot, the OS was already booted, and the player was
logged in without administrator privileges.
All players were using 768-bit RSA keys. These keys
are not strong enough to provide long-term security, but
in our scenario the signatures only need to last until any
cheaters have been identified, i.e., at most a few days or
weeks beyond the end of the game. In December 2009,
factoring a 768-bit number took almost 2,000 Opteron-
CPU years [3], so this key length should be safe for gam-
ing purposes for some time to come.
To quantify the costs of various aspects of AVMs, we
ran experiments in five different configurations. bare-
hw is our baseline configuration in which the game
runs directly on the hardware, without virtualization.
vmware-norec adds the virtual machine monitor with-
out our modifications, and vmware-rec adds the logging
for deterministic replay. avymm_-nosig uses our AVMM
implementation without signatures, and avmm-rsa768
is the full system as described.
We removed the default frame rate cap of 72 fps,
so that Counterstrike rendered frames as quickly as
the available CPU resources allow and we can use the
achieved frame rate as a performance metric. In Sec-
tion 6.5 we consider a configuration with the default
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
(+ Tamper-evident logging
[9 VMware other
Ml «VMware MAC layer
Ma VMware TimeTracker
—ll Total after compression
Average log growth (MB/minute)
o
AVMM
(RSA-768)
VMware
Figure 4: Average log growth for Counterstrike by con-
tent. The bars in front show the size after compression.
frame rate cap. To make sure the performance of bare-
hw and virtualized configurations can be compared, we
configured the game to run without OpenGL, which is
not supported in our version of VMware Workstation,
and we ran the game in window rather than full-screen
mode. We played each game for at least thirty minutes.
6.3 Functionality check
Recall from Section 5.4 that AVMs can detect by design
all of the 26 cheats we examined. As a sanity check to
validate our implementation, we tried four Counterstrike
cheats in our collection that do not depend on OpenGL.
For each cheat, we created a modified VM image that
had the cheat preinstalled, and we ran an experiment in
the avmm-rsa768 configuration where one of the play-
ers used the special VM image and activated the cheat.
We then audited each player; as expected, the audits of
the honest players all succeeded, while the audits of the
cheater failed due to a divergence during replay.
6.4 Log size and contents
The AVMM records a log of the AVM’s execution dur-
ing game play. To determine how fast this log grows,
we played the game in the avmm-rsa768 configuration,
and we measured the log size over time. Figure 3 shows
the results. The log grows slowly while players are join-
ing the game (until about 3 minutes into the experiment)
and then continues to grow steadily during game play,
by about 8 MB per minute. For comparison, we also
show the size of an equivalent VMware log; the differ-
ence is due to the extra information that is required to
make the log tamper-evident.
Figure 4 shows the average log growth rate about the
content. More than 70% of the AVMM log consist of
information needed for replay; tamper-evident logging
is responsible for the rest. The replay information con-
sists mainly of TimeTracker entries (59%), which
are used by the VMM to record the exact timing of
events, and MAC-layer entries (14%), such as incom-
USENIX Association
USENIX Association
ing or outgoing network packets; other entry types ac-
count for the remaining 27%. The composition of the
VMware log differs slightly because the packet payloads
are stored in the MAC-layer entries rather than in the
tamper-evident logging entries. We also show results af-
ter applying bzip2 and a lossless, VMM-specific (but
application-independent) compression algorithm we de-
veloped. This brings the average log growth rate to
2.47 MB per minute.
From these results, we can estimate that a one-hour
game session would result in a 480 MB log, or 148 MB
after compression. Thus, given that current hard disk
capacities are measured in terabytes, storage should not
be a problem, even for very long games. Also, when a
player is audited, he must upload his log to his fellow
players. If the game is played over the Internet, upload-
ing a one-hour log would take about 21 minutes over
a 1 Mbps upstream link. If the game is played over a
LAN, e.g., at a competition, the upload would complete
in a few seconds. To avoid detection delays, our pro-
totype can also perform auditing concurrently with the
game; we evaluate this feature in Section 6.11.
6.5 Low growth with the frame rate cap
Recall that Counterstrike was configured without a
frame rate cap in our experiments, so that the mea-
sured frame rate can be used as a performance met-
ric. We discovered that when the frame rate cap is en-
abled, Counterstrike appears to implement inter-frame
delays by busy-waiting in a tight loop, reading the sys-
tem clock. Since the AVMM has to log every clock ac-
cess as a nondeterministic input, this increases the log
growth considerably — by a factor of 18 when the default
cap of 72 fps is used.
To reduce the log growth for applications that exhibit
this behavior, we experimented with the following opti-
mization. Whenever the AVMM observes consecutive
clock reads from the same AVM within 5 ys of each
other, it delays the n.th consecutive read by 2”~?«50 pus,
starting with the second read and up to a limit of 5 ms.
The exponential progression of delays limits the number
of clock reads during long waits, but does not unduly af-
fect timing accuracy during short waits.
This optimization is very effective: log growth is ac-
tually 2% lower than reported in Section 6.4, with or
without the frame-rate cap. Moreover, the uncapped
frame rate is only 3% lower than the rate without the op-
timization, which shows that the optimization has only
a mild impact on game performance.
6.6 Syntactic and semantic check
Alice can audit another player Bob by checking Bob’s
log against his authenticators (syntactic check) and by
replaying Bob’s log using a trusted copy of the VM im-
10
mas Baseline
gas With VMM
2 8 5 :- With AVMM
2
= 6
2
2
S 4
£
2
a 2 ]
Bare VMware VMware AVMM AVMM
hardware (recording) (nosig) (RSA-768)
Figure 5: Median ping round-trip times. The error bars
show the 5th and the 95th percentile.
age (semantic check). We expect the syntactic check
to be relatively fast, since it is essentially a matter of
verifying signatures, whereas the replay involves repeat-
ing all the computations that were performed during the
original game and should therefore take about as long
as the game itself. Our experiments with the log of
the server machine from the avmm-rsa768 configuration
(which covers 2,216 seconds with 1,987 seconds of ac-
tual game play) confirm this. We needed 34.7 seconds
to compress the log, 13.2 seconds to decompress it, 6.9
seconds for the syntactic check, and 1,977 seconds for
the semantic check (2,031 seconds total). Replay was
actually a bit faster because the AVMM skips any time
periods in the recording during which the CPU was idle,
e.g., before the game was started.
Unlike the performance of the actual game, the speed
of auditing is not critical because it can be performed
at leisure, e.g., in the background while the machine is
used for something else.
6.7 Network traffic
The AVMM increases the amount of network traffic for
two reasons: First, it adds a cryptographic signature
to each packet, and second, it encapsulates all packets
in a TCP connection. To quantify this overhead, we
measured the raw, IP-level network traffic in the bare-
hw configuration and in the avmm-rsa768 configuration.
On average, the machine hosting the game sent 22 kbps
in bare-hw and 215.5 kbps in avmm-rsa768.
This high relative increase is partly due to the fact
that Counterstrike clients send extremely small packets
of 50-60 bytes each, at 26 packets/sec, so the AVMM’s
fixed per-packet overhead (which includes one crypto-
graphic signature for each packet and one for each ac-
knowledgment) has a much higher impact than it would
for packets of average Internet packet size. However,
in absolute terms, the traffic is still quite low and well
within the capabilities of even a slow broadband up-
stream.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) = 129
130
100 %
: (==> Hyperthreads
=i aoe (entire CPU)
a 12.5%
80 %
60 %
40%
20% | |
0%
Bare VMware VMware AVMM AVMM
hardware (recording) (nosig) (RSA-768)
Average utilization
Figure 6: Average CPU utilization in Counterstrike for
each of the eight hyperthreads, and for the entire CPU.
6.8 Latency
The AVMM adds some latency to packet transmissions
because of the logging and processing of authenticators.
To quantify this, we ran an AVM in five different con-
figurations and measured the round-trip time (RTT) of
100 ICMP Echo Request packets. Figure 5 shows the
median RTT, as well as the Sth and the 95th percentile.
Since our machines are connected to the same switch,
the RTT on bare hardware is only 192 ys; adding vir-
tualization increases it to 525 pus, VMware recording to
621 jus, and the daemon to above 2 ms. Enabling 768-bit
RSA signatures brings the total RTT to about 5 ms. Re-
call that both the ping and the pong are acknowledged,
so four signatures need to be generated and verified.
Since the critical threshold for interactive applications is
well above 100 ms [12], 5 ms seem tolerable for games.
The overhead could be reduced by using a signing al-
gorithm such as ESIGN [34], which can generate and
verify a 2046-bit signature in less than 125 us.
6.9 CPU utilization
Compared to a Counterstrike game on bare hardware,
the AVMM requires additional CPU power for virtual-
ization and for the tamper-evident log. To quantify this
overhead, we measured the CPU utilization in five con-
figurations, ranging from bare-hw to avmm-rsa768. To
isolate the contribution from the tamper-evident log, we
pinned the daemon process to hyperthread 0 (HT 0) in
the AVMM experiments and restricted the game to the
other hyperthreads except for HT 0’s hypertwin, HT 4,
which shares a core with HT 0.7 One of the machines
in our experiments runs the Counterstrike server in ad-
dition to serving a player. To be conservative, we report
numbers for this machine, as it has the highest load.
Figure 6 shows the average utilization for each HT,
as well as the average across the entire CPU. The uti-
lization of HT 0 (below 8%) in the AVM experiments
2Nevertheless, the load on HT 4 is not exactly zero because Linux
performs kernel-level IRQ handling on lightly-loaded hyperthreads.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
200 ;
mms Baseline
mas With VMM
23:_ With AVMM
150
100
5
0
Bare VMware VMware AVMM AVMM
hardware (recording) (nosig) (RSA768)
Average frame rate
Oo
Figure 7: Frame rate in Counterstrike for each of the
three machines. The left machine was hosting the game.
shows that the overhead from the tamper-evident log is
low. The game is constantly busy rendering frames, but
because the Counterstrike rendering engine is single-
threaded, it cannot run on more than one HT at a time.
The OS/VMM will sometimes schedule it on one HT
and sometimes on another, thus we expect an average
utilization over the eight HTs of 12.5%, which our re-
sults confirm.
6.10 Frame rate
Since the game is rendering frames as fast as the avail-
able CPU cycles allow, a meaningful metric for the CPU
overhead is the achieved frame rate, which we consider
next. To measure the frame rate, we wrote an AMX
Mod X [1] script that increments a counter every time
a frame is rendered. We read out this counter at the be-
ginning and at the end of each game, and we divided
the difference by the elapsed time. Figure 7 shows our
results for each of the three machines. The results vary
over time and among players, because the frame rate de-
pends on the complexity of the scene being rendered,
and thus on the path taken by each player.
The frame rate on the AVMM is about 13% lower than
on bare hardware. The biggest overhead seems to come
from enabling recording in VMware Workstation, which
causes the average frame rate to drop by about 11%. In
absolute terms, the resulting frame rate (137 fps) is still
very high; posts in Counterstrike forums generally rec-
ommend configuring the game for about 60-80 fps.
To quantify the advantage of running some of the
AVMM logic on a different HT, we ran an additional ex-
periment with both Counterstrike and all AVMM threads
pinned to the same hyperthread. This reduced the aver-
age frame rate by another 11 fps.
6.11 Online auditing
If a game session is long or the stakes are particularly
high, players may wish to detect cheaters well before
the end of the game. In such cases, players can incre-
USENIX Association
USENIX Association
120%
a
Ss
= 10,
5 3S 100%
od oO
ES 80%
a® fo)
S52 60%
38 °
2a 40%
ofc Oo S
PE as © — Spot-check time
2 S 20% ° Linear (for comparison)
=
0% 1 1 iE 1 J
0% 20% 40% 60% 80% 100%
Number of consecutive segments covered by the chunk (k)
120%
= 100%
80%
60%
40%
Total data transferred
(compared to a full audit)
Data transferred
——— Linear (for comparison)
i)
36
xe
0% 1 | f 1 J
0% 20% 40% 60% 80% 100%
Number of consecutive segments covered by the chunk (k)
Figure 9: Efficiency of spot checking. The cost of a spot check is roughly proportional to the size of the checked
chunk, but there is a fixed cost per chunk for transferring the memory and disk snapshots and for data decompression.
2
00 [== Offline auditing
Ga Online auditing
@ 150
£
oO
&
= 100
oO
D
gs
oO
>
<x 50
0
No online One audit Two audits
auditing per player per player
Figure 8: Frame rate for each of the three machines with
zero, one, or two online audits per machine.
mentally audit other players’ logs while the game is still
in progress. In this configuration, which we refer to as
online auditing, cheating could be detected as soon as
the externally visible behavior of the cheater’s machine
deviates from that of the reference machine.
Ifa player uses the same machine to concurrently play
the game and audit other players, the higher resource
consumption can affect game performance. To quantify
this effect, we played the game in the avmm-rsa768 con-
figuration with each player auditing zero, one, or two
other players on the same machine. As before, we mea-
sured the average frame rate experienced by each player.
Figure 8 shows our results. With an increasing num-
ber of players audited, the frame rate drops somewhat,
from 137 fps with no audits to 104 fps with two audits.
However, the drop is less pronounced than expected be-
cause the audits can leverage the unused cores. If the
number of audits a is increased further, we expect the
game performance to eventually degrade with 1/a.
Since replay is slightly slower than the original exe-
cution, auditing falls behind the game by about four sec-
onds per minute of play, even when the audit executes on
an otherwise unloaded machine. To ensure quick detec-
tion even during very long game sessions, we can com-
pensate by artificially slowing down the original execu-
tion. We found that a 5% slowdown was sufficient to
allow the auditor to keep up; this reduced the frame rate
by up to 7 fps. Note that a certain lag can actually be
useful to prevent players from learning each other’s cur-
rent positions or strategies through an audit. In practice,
players may want to disallow audits of the current round
and/or the most recent moments of game play.
6.12 Spot checking
Online games are not a very interesting use case for spot
checking because complete audits are feasible. There-
fore, we set up a simple additional experiment that mod-
els a client/server system — specifically, a MySQL 5.0.51
server in one AVM and a client running MySQL’s
sql-bench benchmark in another. We ran this ex-
periment for 75 minutes in the avmm-rsa768 configura-
tion, and we recorded a snapshot every five minutes. We
found that, on average, our prototype takes 5 seconds to
record a snapshot. The incremental disk snapshots are
between 1.9 MB and 91 MB, while each memory snap-
shot occupies about 530 MB. The reason for the latter
is that VMware Workstation creates a full dump of the
AVM’s main memory (512 MB) for each snapshot. This
could probably be optimized considerably, e.g., using
techniques from Remus [11].
In the following, we refer to the part of the log be-
tween two consecutive snapshots as a segment, and to
k; consecutive segments as a k:-chunk. To quantify the
costs of spot checking, we audited all possible k-chunks
in our log for & € {1,3,5,9,12}, and measured the
amount of data that must be transferred over the net-
work, as well as the time it takes to replay the chunk.
However, we excluded /:-chunks that start at the begin-
ning of the log; these are atypical because a) they are
the only chunks for which no memory or disk snapshots
have to be transferred, and b) they have less activity be-
cause the MySQL server is not yet running at the begin-
ning. We report averages because the results for chunks
with the same value of & never varied by more than 10%.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) — 131
132
Figure 9 shows the results, normalized to the cost of
a full audit. As expected, the cost grows with the chunk
size k; however, there is an additional fixed cost per
chunk for transferring the corresponding memory and
disk snapshots.
6.13. Summary
Having reported our results, we now revisit our three ini-
tial questions. We have demonstrated that our AVMM
works out-of-the-box with Counterstrike, a state-of-the-
art game, and we have shown that it is effective against
real cheats we downloaded from Counterstrike forums
on the Internet. AVMs are not free; they affect various
metrics such as latency, traffic, or CPU utilization, and
they reduce the frame rate by about 13%, compared to
the rate achieved on bare hardware. In return for this
overhead, players gain the ability to audit other players.
Auditing takes time, in some cases as much as the game
itself, but it seems time well spent because it either ex-
poses a cheater or clears an innocent player of suspicion.
AVMs provide this novel capability by combining two
seemingly unrelated technologies, tamper-evident logs
and virtualization.
7 Discussion
7.1 Other applications
AVMs are application-independent and can be used in
applications other than games.
Distributed systems: AVMs can be used to make any
distributed system accountable, simply by executing the
software on each node within an AVM. The node soft-
ware can be arbitrarily complex and available only as a
binary system image. Accountability is useful in dis-
tributed systems where principals have an interest in
monitoring the behavior of other principals’ nodes, and
where post factum detection is sufficient. Such systems
include federated systems where no single entity has
complete control or visibility of the entire system, where
different parties compete (e.g., in an online game, an
auction, or a federated system like the Internet) or where
parties are expected to cooperate but lack adequate in-
centives to do so (e.g., in a peer-to-peer system).
Network traffic accountability: AVMs could also
be useful in detecting advanced forms of malware
that could escape online detection mechanisms. An
AVM, combined with a traffic monitor that records
a machine’s network communication, can capture the
network-observable behavior of a machine, and replay it
later with expensive intrusion detection (e.g., taint track-
ing) in place.
Cloud computing: Another potential application of
AVMs is cloud computing. AVMs can enable cloud cus-
tomers to verify that their software executes in the cloud
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
as expected. AVM are a perfect match for infrastructure-
as-a-service (IaaS) clouds that offer customers a vir-
tual machine. However, AVMs in the cloud face addi-
tional challenges: auditors cannot easily replay the en-
tire execution for lack of resources; accountable services
must be able to interact with non-accountable clients;
and, it may not be practical to sign every single packet.
The first challenge can be addressed with spot checking
(Section 3.5). We plan to address the remaining chal-
lenges in future work.
7.2 Using trust to get stronger guarantees
One of the strengths of AVMs is that they can verify the
integrity of a remote node’s execution without relying
on trusted components. However, if trusted components
are available, we can obtain additional guarantees. We
sketch two possible extensions below.
Secure local input: AVMs cannot detect the hypothet-
ical re-engineered aimbot from Section 5.4 because ex-
isting hardware does not authenticate events from local
input devices, such as keyboards or mice. Thus, a com-
promised AVMM can forge or suppress local inputs, and
even a correct AVMM cannot know whether a given
keystroke was generated by the user or synthesized by
another program, or another machine. This limitation
can be overcome by adding crypto support to the input
devices. For example, keyboards could sign keystroke
events before reporting them to the OS, and an auditor
could verify that the keystrokes are genuine using the
keyboard’s public key. Since most peripherals gener-
ate input at relatively low rates, the necessary hardware
should not be expensive to build.
Trusted AVMM: If we can trust the AVMM that is run-
ning on a remote node, we can detect additional classes
of cheats and attacks, including certain attacks on con-
fidentiality. For example, a trusted AVMM could estab-
lish a secure channel between the AVM and Alice (even
if the software in the AVM does not support encryption)
and thus prevent Bob’s machine from leaking informa-
tion by secretly communicating with other machines. A
trusted AVMM could also prevent wallhacks (see Sec-
tion 5.3) by controlling outside access to the machine’s
graphics card. If trusted hardware, such as memory en-
cryption [40] is available on Bob’s machine, the AVMM
could even prevent Bob from reading information di-
rectly from memory. Remote attestation could be used
to make sure that a trusted AVMM is indeed running on
a remote computer, e.g., using a system like Terra [17].
7.3. Accountability versus privacy
Ideally, an accountability system should disclose to an
auditor only the information strictly required to deter-
mine that the auditee has met his obligations. By this
standard, AVM logs are rather verbose: an AVM records
USENIX Association
USENIX Association
enough information to replay the execution of the soft-
ware it is running. This is a price we pay for the gen-
erality of AVMs—they can detect a large class of faults
in complex software available only in binary form. In
practice, the amount of extra information released can
be controlled.
Let us consider how the extra information captured
in the AVM logs affect Alice and Bob’s privacy. The
log reveals information about actions of Bob’s machine,
but only about the execution inside a given AVM, and
only to approved auditors. In the web service scenario
(Figure 2b), Alice is presumably paying Bob for run-
ning her software in an AVM, so she has every right to
know about the execution of the software. Similarly, it
is not unreasonable to expect players in a game to share
information about their game execution. In either case,
the auditor cannot observe executions the auditee may
be running outside the audited AVM.
Alice and Bob’s privacy may be affected when she
uses part of the log as evidence to demonstrate a fault on
Bob’s machine to a third party. The evidence reveals ad-
ditional information about the AVM, including a snap-
shot, to that party. Therefore, Alice should release evi-
dence only to third parties that have a legitimate need to
know about faults on Bob’s machine. To limit the extra
information released to third parties, Alice can use the
hash tree (Section 4.4) to remove any part of the snap-
shot that is not necessary to replay the relevant segment.
7.4 Replay for multiprocessors
Our prototype AVMM can assign only a single CPU
core for each AVM, because VMware’s deterministic re-
play is limited to uniprocessors. SMP-ReVirt [16] has
recently demonstrated that deterministic replay is also
possible for multiprocessors, but its cost is substantially
higher than the cost of uniprocessor replay. Because
replay is a building block for many important applica-
tions, such as forensics [15], replication [11], and de-
bugging [25], there is considerable interest in develop-
ing more efficient techniques [5, 13, 16, 28, 29]. As
more efficient techniques become available, AVMMs
can directly benefit from them.
7.5 Bug detection
Recall that AVMs define faults as deviations from the
behavior of a reference implementation. If the reference
implementation has a bug and this bug is triggered dur-
ing an execution, it will behave identically during the
replay, and thus it will not be classified as a fault. If
a bug in the reference implementation permits unautho-
rized software modification (e.g., a buffer overflow bug),
then neither the modification itself nor the behavior of
the modified software will be reported as a fault.
Detecting bugs in the reference implementation is
outside the fault model AVMs were designed to detect.
However, deterministic execution replay provides an op-
portunity to use sophisticated runtime analysis tools dur-
ing auditing [10]. In particular, techniques whose run-
time costs are too high for deployment in a live system
could be used during an off-line replay. Taint track-
ing, for instance, can reliably detect the unsafe use of
data that were received from an untrusted source [33],
thus detecting buffer overwrite attacks and other forms
of unauthorized software installation. More generally,
sophisticated runtime techniques can be used during re-
play to detect bugs, vulnerabilities and attacks as part of
a normal audit.
8 Conclusion
Accountable virtual machines (AVM) allow users to au-
dit software executing on remote machines. An AVM
can detect a large and general class of faults, and it pro-
duces evidence that can be verified independently by a
third party. At the same time, an AVM allows the op-
erator of the remote machine to prove whether his ma-
chine is correct. To demonstrate that AVMs are feasi-
ble, we have designed and implemented an AVM mon-
itor based on VMware Workstation and used it to de-
tect real cheats in Counterstrike, a popular online multi-
player game. Players can record their game execution in
a tamper-evident manner at a modest cost in frame rate.
Other players can audit the execution to detect cheats,
either after the game has finished or concurrently with
the game. The system is able to detect all of 26 existing
cheats we examined.
Acknowledgments
We appreciate the detailed and helpful feedback from
Jon Howell, the anonymous OSDI reviewers, and our
shepherd, Mendel Rosenblum. We would like to thank
VMware for making the source code of VMware Work-
station available to us under the VMware Academic Pro-
gram, and our technical contact, Jim Chow, who has
been extremely helpful. Finally, we would like to thank
our many enthusiastic Counterstrike volunteers.
References
[1] AMX Mod X project. http: //www. amxmodx.org/.
[2] D. Andersen, H. Balakrishnan, N. Feamster, T. Koponen,
D. Moon, and S. Shenker. Accountable Internet protocol
(AIP). In Proceedings of the ACM SIGCOMM Conference (SIG-
COMM), Aug. 2008.
[3] K. Aoki, J. Franke, A. K. Lenstra, E. Thomé, J. W. Bos,
P. Gaudry, A. Kruppa, P. L. Montgomery, D. A. Osvik, H. te
Riele, A. Timofeev, and P. Zimmerman. Factorization of a 768-
bit RSA modulus. http://eprint.iacr.org/2010/
006.pdé£.
[4] K. Argyraki, P. Maniatis, O. Irzak, and S. Shenker. An account-
ability interface for the Internet. In Proceedings of the IEEE
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) —-133
134
[5]
[6]
[7]
[8]
[9]
[10]
[11]
[12]
[13]
[14]
[15]
[16]
[17]
[18]
[19]
[20]
[21]
[22]
[23]
[24]
International Conference on Network Protocols (ICNP), Oct.
2007.
A. Aviram, S.-C. Weng, S. Hu, and B. Ford. Efficient system-
enforced deterministic parallelism. In Proceedings of the
USENIX Symposium on Operating System Design and Imple-
mentation (OSDI), Oct. 2010.
P. Barham, B. Dragovic, K. Fraser, S. Hand, T. Harris, A. Ho,
R. Neugebauer, I. Pratt, and A. Warfield. Xen and the art of vir-
tualization. In Proceedings of the ACM Symposium on Operating
Systems Principles (SOSP), Oct. 2003.
N. E. Baughman, M. Liberatore, and B. N. Levine. Cheat-proof
playout for centralized and peer-to-peer gaming. IEEE/ACM
Transactions on Networking (ToN), 15(1):1-13, Feb. 2007.
T. C. Bressoud and F. B. Schneider. Hypervisor-based fault
tolerance. ACM Transactions on Computer Systems (TOCS),
14(1):80-107, 1996.
C. Chambers, W. Feng, W. Feng, and D. Saha. Mitigating infor-
mation exposure to cheaters in real-time strategy games. In Pro-
ceedings of the ACM International Workshop on Network and
operating systems support for digital audio and video (NOSS-
DAV), June 2005.
J. Chow, T. Garfinkel, and P. M. Chen. Decoupling dynamic
program analysis from execution in virtual environments. In
Proceedings of the USENIX Annual Technical Conference, June
2008.
B. Cully, G. Lefebvre, D. Meyer, M. Feeley, N. Hutchinson, and
A. Warfield. Remus: High availability via asynchronous virtual
machine replication. In Proceedings of the USENIX Symposium
on Networked Systems Design and Implementation (NSDI), Apt.
2008.
J. Dabrowski and E. V. Munson. Is 100 milliseconds too fast? In
Proceedings of the ACM SIGCHI Conference on Human Factors
in Computing Systems (CHI), Apr. 2001.
J. Devietti, B. Lucia, L. Ceze, and M. Oskin. DMP: Determinis-
tic shared memory multiprocessing. In Proceedings of the ACM
International Conference on Architectural Support for Program-
ming Languages and Operating Systems (ASPLOS), Mar. 2009.
R. Dingledine, M. J. Freedman, and D. Molnar. Peer-to-Peer:
Harnessing the Power of Disruptive Technologies, chapter Ac-
countability. O’Reilly and Associates, 2001.
G. W. Dunlap, S. T. King, S. Cinar, M. Basrai, and P. M. Chen.
ReVirt: Enabling intrusion analysis through virtual-machine
logging and replay. In Proceedings of the USENIX Symposium
on Operating System Design and Implementation (OSDI), Dec.
2002.
G. W. Dunlap, D. Lucchetti, P. M. Chen, and M. Fetterman. Ex-
ecution replay for multiprocessor virtual machines. In Proceed-
ings of the ACM/USENIX International Conference on Virtual
Execution Environments (VEE), Mar. 2008.
T. Garfinkel, B. Pfaff, J. Chow, M. Rosenblum, and D. Boneh.
Terra: A virtual machine-based platform for trusted computing.
In Proceedings of the ACM Symposium on Operating Systems
Principles (SOSP), Oct. 2003.
A. Haeberlen. A case for the accountable cloud. In Proceedings
of the ACM SIGOPS International Workshop on Large-Scale
Distributed Systems and Middleware (LADIS), Oct. 2009.
A. Haeberlen, P. Aditya, R. Rodrigues, and P. Druschel. Ac-
countable virtual machines. Technical Report 2010-3, Max
Planck Institute for Software Systems, Sept. 2010.
A. Haeberlen, I. Avramopoulos, J. Rexford, and P. Druschel. Ne-
tReview: Detecting when interdomain routing goes wrong. In
Proceedings of the USENIX Symposium on Networked Systems
Design and Implementation (NSDI), Apr. 2009.
A. Haeberlen, P. Kuznetsov, and P. Druschel. PeerReview: Prac-
tical accountability for distributed systems. In Proceedings of
the ACM Symposium on Operating Systems Principles (SOSP),
Oct. 2007.
A. Haeberlen, P. Kuznetsov, and P. Druschel. PeerReview: Prac-
tical accountability for distributed systems. Technical Report
2007-3, Max Planck Institute for Software Systems, Oct. 2007.
G. Hoglund. 4.5 million copies of EULA-compliant spyware.
http://www. rootkit .com/blog.php?newsid=358.
G. Hoglund and G. McGraw. Exploiting Online Games: Cheat-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
[25]
[26]
[27]
[28]
[29]
[30]
[40]
[41]
[42]
[43]
[44]
ing Massively Distributed Systems. Addison-Wesley, 2007.
S. T. King, G. W. Dunlap, and P. M. Chen. Debugging operating
systems with time-traveling virtual machines. In Proceedings of
the USENIX Annual Technical Conference, Apr. 2005.
B. W. Lampson. Computer security in the real world. In Pro-
ceedings of the Annual Computer Security Applications Confer-
ence (ACSAC), Dec. 2000.
P. Laskowski and J. Chuang. Network monitors and contract-
ing systems: competition and innovation. In Proceedings of the
ACM SIGCOMM Conference (SIGCOMM), Sept. 2006.
D. Lee, M. Said, S. Narayanasamy, Z. Yang, and C. Pereira.
Offline symbolic analysis for multi-processor execution replay.
In Proceedings of the IEEE/ACM International Symposium on
Microarchitecture (MICRO), Dec. 2009.
D. Lee, B. Wester, K. Veeraraghavan, S. Narayanasamy, P. M.
Chen, and J. Flinn. Respec: Efficient online multiprocessor re-
play via speculation and external determinism. In Proceedings
of the ACM International Conference on Architectural Support
for Programming Languages and Operating Systems (ASPLOS),
Mar. 2010.
D. Levin, J. R. Douceur, J. R. Lorch, and T. Moscibroda. TrInc:
Small trusted hardware for large distributed systems. In Pro-
ceedings of the USENIX Symposium on Networked Systems De-
sign and Implementation (NSDI), Apr. 2009.
N. Michalakis, R. Soulé, and R. Grimm. Ensuring content in-
tegrity for untrusted peer-to-peer content distribution networks.
In Proceedings of the USENIX Symposium on Networked Sys-
tems Design and Implementation (NSDI), Apr. 2007.
C. Monch, G. Grimen, and R. Midtstraum. Protecting online
games against cheating. In Proceedings of the Workshop on Net-
work and Systems Support for Games (NetGames), Oct. 2006.
J. Newsome and D. Song. Dynamic taint analysis for automatic
detection, analysis, and signature generation of exploits on com-
modity software. In Proceedings of the Annual Network and
Distributed Systems Security Symposium (NDSS), Feb. 2005.
T. Okamoto. A fast signature scheme based on congruential
polynomial operations. EEE Transactions on Information The-
ory, 36(1):47-53, 1990.
PunkBuster web site. http: //www.evenbalance.com/.
A. Seshadri, M. Luk, E. Shi, A. Perrig, L. van Doorn, and
P. Khosla. Pioneer: Verifying code integrity and enforcing un-
tampered code execution on legacy systems. In Proceedings of
the ACM Symposium on Operating Systems Principles (SOSP),
Oct. 2005.
A. Smith. ASUS releases games cheat drivers.
http://www.theregister.co.uk/2001/05/10/
asus_releases_games_cheat_drivers/, May 2001.
Valve Corporation. Valve anti-cheat system (VAC). https:
//support .steampowered.com/kb-article.php?
ref=7849-RADZ- 6869.
M. Xu, V. Malyugin, J. Sheldon, G. Venkitachalam, and
B. Weissman. ReTrace: Collecting execution trace with vir-
tual machine deterministic replay. In Proceedings of the Annual
Workshop on Modeling, Benchmarking, and Simulation (MoBS),
June 2007.
C. Yan, D. Englender, M. Prvulovic, B. Rogers, and Y. Solihin.
Improving cost, performance, and security of memory encryp-
tion and authentication. ACM SIGARCH Computer Architecture
News, 34(2):179-190, 2006.
J. Yan and B. Randell. A systematic classification of cheating in
online games. In Proceedings of the Workshop on Network and
Systems Support for Games (NetGames), Oct. 2005.
S. Yang, A. R. Butt, Y. C. Hu, and S. P. Midkiff. Trust but
verify: Monitoring remotely executing programs for progress
and correctness. In Proceedings of the ACM SIGPLAN Annual
Symposium on Principles and Practice of Parallel Programming
(PPoPP), June 2005S.
A. R. Yumerefendi and J. S. Chase. Trust but verify: Account-
ability for Internet services. In Proceedings of the ACM SIGOPS
European Workshop, Sep 2004.
A. R. Yumerefendi and J. S. Chase. Strong accountability for
network storage. ACM Transactions on Storage (TOS), 3(3):11,
Oct. 2007.
USENIX Association
USENIX Association
Bypassing Races in Live Applications with Execution Filters
Jingyue Wu, Heming Cui, Junfeng Yang
{jingyue, heming, junfeng} @cs.columbia.edu
Computer Science Department
Columbia University
New York, NY 10027
Abstract
Deployed multithreaded applications contain many races
because these applications are difficult to write, test, and
debug. Worse, the number of races in deployed applica-
tions may drastically increase due to the rise of multicore
hardware and the immaturity of current race detectors.
LOOM is a “live-workaround” system designed to
quickly and safely bypass application races at runtime.
LOOM provides a flexible and safe language for develop-
ers to write execution filters that explicitly synchronize
code. It then uses an evacuation algorithm to safely in-
stall the filters to live applications to avoid races. It re-
duces its performance overhead using hybrid instrumen-
tation that combines static and dynamic instrumentation.
We evaluated LOOM on nine real races from a diverse
set of six applications, including MySQL and Apache.
Our results show that (1) LOOM can safely fix all evalu-
ated races in a timely manner, thereby increasing appli-
cation availability; (2) LOOM incurs little performance
overhead; (3) LOOM scales well with the number of ap-
plication threads; and (4) LOOM is easy to use.
1 Introduction
Deployed multithreaded applications contain many races
because these applications are difficult to write, test, and
debug. These races include data races, atomicity viola-
tions, and order violations [33]. They can cause applica-
tion crashes and data corruptions. Worse, the number of
“deployed races” may drastically increase due to the rise
of multicore and the immaturity of race detectors.
Many previous systems can aid race detection (e.g.,
[31, 32, 37, 47, 54]), replay [9, 18, 28, 36, 43], and diag-
nosis [42, 49]. However, they do not directly address de-
ployed races. A conventional solution to fixing deployed
races is software update, but this method requires appli-
cation restarts, and is at odds with high availability de-
mand. Live update systems [10, 12, 15, 35, 38, 39, 51]
can avoid restarts by adapting conventional patches into
hot patches and applying them to live systems, but the
reliance on conventional patches has two problems.
First, due to the complexity of multithreaded applica-
tions, race-fix patches can be unsafe and introduce new
errors [33]. Safety is crucial to encourage user adoption,
yet automatically ensuring safety is difficult because con-
ventional patches are created from general, difficult-to-
analyze languages. Thus, previous work [38, 39] had to
resort to extensive programmer annotations.
Second, creating a releasable patch from a correct di-
agnosis can still take time. This delay leaves buggy ap-
plications unprotected, compromising reliability and po-
tentially security. This delay can be quite large: we an-
alyzed the Bugzilla records of nine real races and found
that this delay can be days, months, or even years. Ta-
ble 1 shows the detailed results.
Many factors contribute to this delay. At a minimum
level, a conventional patch has to go through code re-
view, testing, and other mandatory software develop-
ment steps before being released, and these steps are all
time-consuming. Moreover, though a race may be fixed
in many ways (e.g., lock-free flags, fine-grained locks,
and coarse-grained locks), developers are often forced to
strive for an efficient option. For instance, two of the
bugs we analyzed caused long discussions of more than
30 messages, yet both can be fixed by adding a single
critical section. Performance pressure is perhaps why
many races were not fixed by adding locks [33].
This paper presents LOOM, a “live-workaround” sys-
tem designed to quickly protect applications against
races until correct conventional patches are available and
the applications can be restarted. It reflects our belief
that the true power of live update is its ability to pro-
vide immediate workarounds. To use LOOM, developers
first compile their application with LOOM. At runtime,
to workaround a race, an application developer writes an
execution filter that synchronizes the application source
to filter out racy thread interleavings. This filter is kept
separate from the source. Application users can then
download the filter and, for immediate protection, install
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) —:135
136
Race ID Report —_ Diagnosis Fix Release
Apache-25520 12/15/03 12/18/03 01/17/04 ~——-03/19/04
Apache-21287 07/02/03 N/A 12/18/03 03/19/04
Apache-46215 11/14/08 N/A N/A N/A
MySQL-169 03/19/03 N/A 03/24/03 06/20/03
MySQL-644 06/12/03 N/A N/A 05/30/04
MySQL-791 07/04/03. =: 07/04/03. 07/14/03 ~——- 07/22/03
Mozilla-73761 03/28/01 03/28/01 04/09/01 05/07/01
Mozilla-201134 04/07/03 = 04/07/03 ~—s: 04/16/03 —- 01/08/04
Mozilla-133773 03/27/02 ~—- 03/27/02 12/01/09 01/21/10
Table 1: Long delays in race fixing. We studied the delays
in the fix process of nine real races; some of the races were
extensively studied [9, 31, 33, 42, 43]. We identify each race
by “Application — (Bugzilla #).” Column Report indicates
when the race was reported, Diagnosis when a developer con-
firmed the root cause of the race, Fix when the final fix was
posted, and Release when the version of application contain-
ing the fix was publicly released. We collected all dates by
examining the Bugzilla record of each race. An N/A means
that we could not derive the date. The days between diagno-
sis and fix range from a few days to a month to a few years.
For all but two races, the bug reports from the application users
contained correct and precise diagnoses. Mozilla-201134 and
Mozilla-133773 caused long discussions of more than 30 mes-
sages, though both can be fixed by adding a critical region.
it to their application without a restart.
Loom decouples execution filters from application
source to achieve safety and flexibility. Execution fil-
ters are safe because LOOM’s execution filter language
allows only well formed synchronization constraints. For
instance, “code region r; and rg are mutually exclu-
sive.” This declarative language is simpler to analyze
than a general programing language such as C because
LOOM need not reverse-engineer developer intents (e.g.,
what goes into a critical region) from scattered opera-
tions (e.g., Lock () and unlock ()).
As temporary workarounds, execution filters are more
flexible than conventional patches. One main benefit is
that developers can make better performance and relia-
bility tradeoffs during race fixing. For instance, to make
two code regions 7; and 72 mutually exclusive when they
access the same memory object, developers can use crit-
ical regions larger than necessary; they can make 7; and
rg always mutually exclusive even when accessing dif-
ferent objects; or in extreme cases, they can run 7; and r2
in single-threaded mode. This flexibility enables quick
workarounds; it can benefit even the applications that do
not need live update.
We believe the execution filter idea and the LOOM
system as described are worthwhile contributions. To
the best of our knowledge, LOOM is the first live-
workaround system designed for races. Our additional
technical contributions include the techniques we created
to address the following two challenges.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
A key safety challenge Loom faces is that even if
an execution filter is safe by construction, installing it
to a live application can still introduce errors because
the application state may be inconsistent with the filter.
For instance, if a thread is running inside a code region
that an execution filter is trying to protect, a “double-
unlock” error could occur. Thus, LOOM must (1) check
for inconsistent states and (2) install the filter only in
consistent ones. Moreover, LOOM must make the two
steps atomic, despite the concurrently running applica-
tion threads and multiple points of updates. This problem
cannot be solved by a common safety heuristic called
function quiescence [2, 13, 21, 39]. We thus create a
new algorithm termed evacuation to solve this problem
by proactively quiescing an arbitrary set of code regions
given at runtime. We believe this algorithm can also ben-
efit other live update systems.
A key performance challenge LOOM faces is to main-
tain negligible performance overhead during an appli-
cation’s normal operations to encourage adoption. The
main runtime overhead comes from the engine used to
live-update an application binary. Although LOOM can
use general-purpose binary instrumentation tools such as
Pin, the overhead of these tools (up to 199% [34] and
1065.39% in our experiments) makes them less suitable
as options for LOOM. We thus create a hybrid instrumen-
tation engine to reduce overhead. It statically transforms
an application to include a “hot backup”, which can then
be updated arbitrarily by execution filters at runtime.
We implemented LOOM on Linux. It runs in user
space and requires no modifications to the applications
or the OS, simplifying deployment. It does not rely on
non-portable OS features (e.g., SIGSTOP to pause appli-
cations, which is not supported properly on Windows).
LOOM’s static transformation is a plugin to the LLVM
compiler [3], requiring no changes to the compiler either.
We evaluated LOOM on nine real races from a diverse
set of six applications: two server applications, MySQL
and Apache; one desktop application PBZip2 (a parallel
compression tool); and implementations of three scien-
tific algorithms in SPLASH2 [7]. Our results show that
1. LOoM is effective. It can flexibly and safely fix all
races we have studied. It does not degrade applica-
tion availability when installing execution filters. Its
evacuation algorithm can install a fix within a second
even under heavy workload, whereas a live update
approach using function quiescence cannot install the
fix in an hour, the time limit of our experiment.
2. LOOM is fast. LOOM has negligible performance
overhead and in some cases even speeds up the ap-
plications. The one exception is MySQL. Running
MySQL with Loom alone increases response time
by 4.11% and degrades throughput by 3.76%.
3. LOOM is scalable. Experiments on a 48-core ma-
USENIX Association
USENIX Association
Static preparation Live update
EDL |
Application
Source
LLVM
Compiler
Control Flaw
Graphs
pp] Symbol info | | | Safe
9 eageaeee | Update f
' Plan
> Application
Binary
Safely
Update
=>
Patched
Application
Buggy
Application
Figure 1: LOOM overview. Its components are shaded.
chine show that LOOM scales well as the number of
application threads increases.
4. LOOM is easy to use. Execution filters are concise,
safe, and flexible (able to fix all races studied, often
in more than one way).
This paper is organized as follows. We first give an
overview of LOOM (§2). We then describe LOOM’s exe-
cution filter language (§3), the evacuation algorithm (84),
and the hybrid instrumentation engine (85). We then
present our experimental results (§6). We finally discuss
related work (§7) and conclude (88).
2 Overview
Figure | presents an overview of LOOM. To use LOOM
for live update, developers first statically transform their
applications with LOOM’s compiler plugin. This plugin
injects a copy of LOOM’s update engine into the applica-
tion binary; it also collects the application’s control flow
graphs (CFG) and symbol information on behalf of the
live update engine.
Loom’s compiler plugin runs within the LLVM com-
piler [3]. We choose LLVM for its compatibility with
GCC and its easy-to-analyze intermediate representation
(IR). However, LOOM’s algorithms are general and can
be ported to other compilers such as GCC. Indeed, for
clarity we will present all our algorithms at the source
level (instead of the LLVM IR level).
To fix arace, application developers write an execution
filter in LOOM’s filter language and distribute the filter
to application users. A user can then install the filter to
immediately protect their application by running
% loomctl add <pid> <filter-file>
Here loomctl is a user-space program called the
LOOM controller that interacts with users and initiates
live update sessions, pid denotes the process ID of a
buggy application instance, and filter-fileisa file
containing the execution filter. Under the hood, this con-
troller compiles the execution filter down to a safe update
plan using the CFGs and symbol information collected
by the compiler plugin. This update plan includes three
parts: (1) synchronization operations to enforce the con-
straints described in the filter and where, in the applica-
tion, to add the operations; (2) safety preconditions that
must hold for installing the filter; and (3) sanity checking
code to detect potential errors in the filter itself. The con-
troller sends the update plan to the update engine running
as a thread inside the application’s address space, which
then monitors the runtime states of the application and
carries out the update plan only when all the safety pre-
conditions are satisfied.
If LOOM detects a problem with a filter through one of
its sanity checks, it can automatically remove the prob-
lematic filter. It again waits for all the safety precondi-
tions to hold before removing the filter.
Users can also remove a filter manually, if for exam-
ple, the race that the filter intends to fix turns out to be
benign. They do so by running
oe
loomctl ls <pid>
% loomctl remove <pid> <filter-id>
The first command “loomct1 1s” returns a list of
installed filter IDs within process pid. The sec-
ond command “loomctl remove” removes filter
filter—id from process pid.
Users can replace an installed filter with a new filter,
if for example the new filter fixes the same race but has
less performance overhead. Users do so by running
% loomctl replace <pid> <old-id> <new-file>
where old-id is the ID of the installed filter, and
new-file is a file containing the new filter. LOOM
ensures that the removal of the old filter and the installa-
tion of the new filter are atomic, so that the application is
always protected from the given race.
2.1 Usage Scenarios
LOOM enables users to explicitly describe their synchro-
nization intents and orchestrate thread interleavings of
live applications accordingly. Using this mechanism, we
envision a variety of strategies users can use to fix races.
Live update At the most basic level, users can translate
some conventional patches into execution filters, and use
LOOM to install them to live applications.
Temporary workaround Before a permanent fix (i.e., a
correct source patch) is out, users can create an execu-
tion filter as a crude, temporary fix to a race, to provide
immediate protection to highly critical applications.
Preventive fix When a potential race is reported (e.g.,
by automated race detection tools or users of the appli-
cation), users can immediately install a filter to prevent
the race suspect. Later, when developers deem this report
false or benign, users can simply remove the filter.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) —-137
138
Cooperative fix Users can share filters with each other.
This strategy enjoys the same benefits as other coopera-
tive protection schemes [17, 26, 44, 50]. One advantage
of LOOM over some of these systems is that it automat-
ically verifies filter safety, thus potentially reducing the
need to trust other users.
Site-specific fix Different sites have different workloads.
An execution filter too expensive for one site may be fine
for another. The flexibility of execution filters allows
each site to choose what specific filters to install.
Fix without live update For applications that do not
need live update, users can still use LOOM to create quick
workarounds, improving reliability.
Besides fixing races, LOOM can be used for the op-
posite: demonstrating a race by forcing a racy thread in-
terleaving. Compared to previous race diagnosis tools
that handle a fixed set of race patterns [25, 41, 42, 49],
LOOM’s advantage is to allow developers to construct
potentially complex “concurrency” testcases.
Although LOoM can also avoid deadlocks by avoid-
ing deadlock-inducing thread interleavings, it is less suit-
able for this purpose than existing tools (e.g., Dimmu-
nix [26]). To avoid races, LOOM’s update engine can add
synchronizations to arbitrary program locations. This en-
gine is overkill for avoiding deadlocks: intercepting lock
operations (e.g., via LD_-PRELOAD) is often enough.
2.2 Limitations
LOOM is explicitly designed to work around (broadly de-
fined) races because they are some of the most difficult
bugs to fix and this focus simplifies LOOM’s execution
filter language and safety analysis. LOOM is not intended
for other classes of errors. Nonetheless, we believe the
idea of high-level and easy-to-verify fixes can be gener-
alized to many other classes of errors.
LOOM does not attempt to fix occurred races. That
is, if a race has caused bad effects (e.g., corrupted data),
LOOM does not attempt to reverse the effects (e.g., re-
cover the data). It is conceivable to allow developers to
provide a general function that LOOM runs to recover
occurred races before installing a filter. Although this
feature is simple to implement, it makes safety analysis
infeasible. We thus rejected this feature.
Safety in LOOM terms means that an execution filter
and its installation/removal processes introduce no new
correctness errors to the application. However, similar
to other safe error recovery [46] or avoidance [26, 52]
tools, LOOM runs with the application and perturbs tim-
ing, thus it may expose some existing application races
because it makes some thread interleavings more likely
to occur. Moreover, execution filters synchronize code,
and may introduce deadlocks and performance prob-
lems. LOOM can recover from filter-introduced dead-
locks (83.3) using timeouts, but currently does not deal
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
with performance problems.
At an implementation level, LOOM currently supports
a fixed set of synchronization constraint types. Although
adding new types of constraints is easy, we have found
the existing constraint types sufficient to fix all races
evaluated. Another issue is that LOOM uses debugging
symbol information in its analysis, which can be inaccu-
rate due to compiler optimization. This inaccuracy has
not been a problem for the races in our evaluation be-
cause LOOM keeps an unoptimized version of each basic
block for live update (§5).
3 Execution Filter Language
LOOM’s execution filter language allows developers to
explicitly declare their synchronization intents on code.
This declarative approach has several benefits. First,
it frees developers from the low-level details of syn-
chronization, increasing race fixing productivity. Sec-
ond, it also simplifies LOOM’s safety analysis because
LOOM does not have to reverse-engineer developer in-
tents (e.g., what goes into a critical section) from low-
level synchronization operations (e.g., scattered Lock ()
and unlock () ), which can be difficult and error-prone.
Lastly, LOOM can easily insert error-checking code for
safety when it compiles a filter down to low-level syn-
chronization operations.
3.1 Example Races and Execution Filters
In this section, we present two real races and the execu-
tion filters to fix them to demonstrate LOOM’s execution
filter language and its flexibility.
The first race is in MySQL (Bugzilla # 791), which
causes the MySQL on-disk transaction log to miss
records. Figure 2 shows the race. The code on the
left (function new_file () ) rotates MySQL’s transac-
tion log file by closing the current log file and opening
a new one; it is called when the transaction log has to
be flushed. The code on the right is used by MySQL to
append a record to the transaction log. It uses double-
checked locking and writes to the log only when the log
is open. The race occurs if the racy is_open() (T2,
line 3) catches a closed log when thread T1 is between
the close () (TI, line 5) and the open () (TI, line 6).
Although a straightforward fix to the race exists, per-
formance demands likely forced developers to give up
the fix and choose a more complex one instead. The
straightforward fix should just remove the racy check
(T2, line 3). Unfortunately, this fix creates unneces-
sary overhead if MySQL is configured to skip logging
for speed; this overhead can increase MySQL’s response
time by more than 10% as observed in our experiments.
The concern to this overhead likely forced MySQL de-
velopers to use a more involved fix, which adds a new
flag field to MySQL’s transaction log and modifies the
USENIX Association
1:
2:
USENIX Association
OO SD Or 09
// log.cc. thread T1
void MYSQL_LOG::new-file(){
lock(&LOCK_log);
1: / sql_insert.cc. thread T2
2: // [race] may return false
3: if (mysql_bin_log.is_open()){
lock(&LOCK_log);
if (mysql_bin_log.is_open()){
... 1 write to log
close(); // log is closed
open(. . .);
ON Os
unlock(&LOCK log);
} 9: }
Figure 2: A real MySQL race, slightly modified for clarity.
unlock(&LOCK_log);
// Execution filter 1: unilateral exclusion
{log.ce:5, log.cc:}6} <> *
// Execution filter 2: mutual exclusion of code
{log.ce:5, log.cc:6} <> MYSQL-_-LOG::is_open
// Execution filter 3: mutual exclusion of code and data
{log.cc:5 (this), log.cc:6 (this)} <> MYSQL-_LOG::is_open(this)
Figure 3: Execution filters for the MySQL race in Figure 2.
close() function to distinguish a regular close ()
call and one for reopening the log.
In contrast, LOOM allows developers to create tem-
porary workarounds with flexible performance and reli-
ability tradeoffs. These temporary fixes can protect the
application until developers create a correct and efficient
fix at the source level. Figure 3 shows several execu-
tion filters that can fix this race. Execution filter 1 in the
figure is the most conservative fix: it makes the code re-
gion between TI, line 5 and T1, line 6 atomic against
all code regions, so that when a thread executes this re-
gion, all other threads must pause. We call such a syn-
chronization constraint unilateral exclusion in contrast to
mutual exclusion that requires participating threads agree
on the same lock.' Here operator “<>” expresses mutual
exclusion constraints, its first operand “{log.cc:5,
log.cc:6}” specifies a code region to protect, and its
second operand “x” represents all code regions. This
“expensive” fix incurs only 0.48% overhead (86.1) be-
cause the log rotation code rarely executes.
Execution filter 2 reduces overhead by refining
the “x” operand to a specific code region, function
MYSQL_LOG: :is_open(). This filter makes the two
code regions mutually exclusive, regardless of what
memory locations they access. Execution filter 3 further
improves performance by specifying the memory loca-
tion accessed by each code region.
The second race causes PBZip2 to crash due to a use-
after-free error. Figure 4 shows the race. The crash oc-
curs when fifo is dereferenced (line 10) after it is freed
(line 5). The reason is that the main () thread does not
wait for the decompress () threads to finish. To fix
this race, developers can use the filter in Figure 5, which
constrains line 10 to run for numCPU times before line 5.
'Note that unilateral exclusion differs (subtly) from single-threaded
execution: unilateral exclusion allows no context switches.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
// pbzip2.cpp. thread T2
7 : void *“decompress(void *q){
queue “fifo = (queue *)q;
// pbzip2.cpp. thread TI
1: main() {
2: for(i=0;i<numCPU;i++) : :
3 pthread_create(. .., Daas
4: decompress, fifo); 10: — pthread_mutex_lock(fifo—>mut);
5 queueDelete(fifo); TT sas
6: 12: }
Figure 4: A real PBZip2 race, simplified for clarity.
pbzip2.cpp:10 {numCPU} > pbzip2.cpp:5
Figure 5: Execution filter for the PBZip2 race in Figure 4.
3.2 Syntax and Semantics
Table 2 summarizes the main syntax and semantics of
Loom’s execution filter language. This language al-
lows developers to express synchronization constraints
on events and regions. An event in the simplest form is
“file : line,’ which represents a dynamic instance of
a static program statement, identified by file name and
line number. An event can have an additional “(expr)”
component and an “{n}” component, where expr and
n refer to valid expressions with no function calls or
dereferences. The expr expression distinguishes differ-
ent dynamic instances of program statements and LOOM
synchronizes the events only with matching expr values.
The n expression specifies the number of occurrences of
an event and is used in execution order constraints. A
region represents a dynamic instance of a static code re-
gion, identified by a set of entry and exist events or an
application function. A region representing a function
call can have an additional “(args)” component to dis-
tinguish different calls to the same function.
LOOM currently supports three types of synchroniza-
tion constraints (the bottom three rows in Table 2). Al-
though adding new constraint types is easy, we have
found existing ones enough to fix all races evaluated. An
execution order constraint as shown in the table makes
event e; happen before e2, e2 before e3, and so forth. A
mutual exclusion constraint as shown makes every pair
of code regions r; and r; mutually exclusive with each
other. A unilateral exclusion constraint conceptually
makes the execution of a code region single-threaded.
3.3. Language Implementation
LOOM implements the execution filter language using
locks and semaphores. Given an execution order con-
straint e; > e€;,1, LOOM inserts a semaphore up () op-
eration at e; and a down () operation at e;4;. LOOM
implements a mutual exclusion constraint by inserting
lock () at region entries and unlock () at region ex-
its. LOOM implements a unilateral exclusion constraint
reusing the evacuation mechanism (84), which can pause
threads at safe locations and resume them later.
139
140
Constructs Syntax
Event (short as e) fue ‘ uae
file : line (expr)
e{n}, nis # of occurrence
POF 010 CF CRT octyl
func (args)
€1 > 62 >... > en
T1 <> 72. <> ae SS Tn
r<>*
Region (short as r)
Execution Order
Mutual Exclusion
Unilateral Exclusion
Table 2: Execution filter language summary.
LOOM creates the needed locks and semaphores on
demand. The first time a lock or semaphore is refer-
enced by one of the inserted synchronization operations,
LOOM creates this synchronization object based on the
ID of the filter, the ID of the constraint, and the value of
expr if present. It initializes a lock to an unlocked state
and a semaphore to 0. It then inserts this object into a
hash table for future references. To limit the size of this
table, LOOM garbage-collects these synchronization ob-
jects. Freeing a synchronization object is safe as long
as it is unlocked (for locks) or has a counter of O (for
semaphores). If this object is referenced later, LOOM
simply re-creates it. The default size of this table is 256
and LOOM never needed to garbage-collect synchroniza-
tion objects in our experiments.
The up () and down () operations LOOM inserts be-
have slightly differently than standard semaphore oper-
ations when n, the number of occurrences, is specified.
Given el{ni} > e2{n2}, up () conceptually increases
the semaphore counter by a and down() decreases
it by at Our implementation uses integers instead of
floats. LOOM stores the value of n the first time the cor-
responding event runs and ignores future changes of n.
LOOM computes the values of expr and n using de-
bugging symbol information. We currently allow expr
and n to be the following expressions: a (constant or
primitive variable), atb, &a, &a[i], &a—>f, or any
recursive combinations of these expressions. For safety,
we do not allow function calls or dereferences. These
expressions are sufficient for writing the execution filters
in our evaluation.
We implemented this feature using the DWARF li-
brary and the parse_exp_1() function in GDB.
Specifically, we use parse_exp_1 () to parse the expr
or m component into an expression tree, then compile this
tree into low level instructions by querying the DWARF
library. Note this compilation step is done inside the
LOOM controller, so that the live update engine does not
have to pay this overhead.
LOOM implements three mechanisms for safety. First,
by keying synchronization objects based on filter and
constraint IDs, it uses a disjoint set of synchronization
objects for different execution filters and constraints,
avoiding interference among them. Second, LOOM in-
serts additional checking code when it generates the up-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
Double unlock Up skipped Wakeup wrong thread
\
Lock PC p\ PC
Up Up
PC SY \ /
Down Down
Unlock m\ PC | em PC
\ \
Figure 6: Unsafe program states for installing filters.
date plan. For example, given a code region c in a mu-
tual exclusion constraint, LOOM checks for errors such
as c’s unlock () releasing a lock not acquired by c’s
lock (). Lastly, LOoM checks for filter-induced dead-
locks to guard against buggy filters. If a buggy filter
introduces a deadlock, one of its synchronization oper-
ations must be involved in the wait cycle. LOOM de-
tects such deadlocks using timeouts, and automatically
removes the offending filter.
4 Avoiding Unsafe Application States
Figure 6 shows three unsafe scenarios LOOM must han-
dle. For a mutual exclusion constraint that turns code
regions into critical sections, LOOM must ensure that
no thread is executing within the code regions when in-
stalling the filter to avoid “double-unlock” errors. Simi-
larly, for an execution order constraint e; > e2, LOOM
must ensure either of the following two conditions when
installing the filter: (1) both e; and eg have occurred or
(2) neither has occurred; otherwise the up () LOOM in-
serts at e; may get skipped or wake up a wrong thread.
Note that a naive approach is to simply ignore an
unlock() if the corresponding lock is already un-
locked, but this approach does not work with execution
order constraints. Moreover, it mixes unsafe program
states with buggy filters, and may reject correct filters
simply because it tries to install the filters at unsafe pro-
gram states.
A common safety heuristic called function quies-
cence [2, 13, 21, 39] cannot address this unsafe state
problem. This technique updates a function only when
no stack frame of this function is active in any call stack
of the application. Unfortunately, though this technique
can ensure safety for many live updates, it is insufficient
for execution filters because their synchronization con-
straints may affect multiple functions.
We demonstrate this point using a race example. Fig-
ure 7 shows the worker thread code of a contrived
database. Function process_client () 1s the main
thread function. It takes a client socket as input and re-
peatedly processes requests from the socket. For each
request, function process_client () opens the cor-
responding database table by calling open_table(),
serves the request, and closes the table by calling
USENIX Association
USENIX Association
1: YW database worker thread
2: void handle_client(int fd) {
3: for(::) {
4: struct client_req req;
5 int ret = recv(fd, &req, ...);
6: if(ret <= 0) break;
C3 open_table(req.table_id);
8: ... / do real work
9: close_table(req.table_id);
10: }
11:
12: void open_table(int table_id) {
13: // fix: acquire table lock
14: ... 4/ actual code to open table
15:
16: void close_table(int table_id) {
17: ... // actual code to close table
18: // fix: release table lock
19: }
Figure 7: A contrived race.
close_table(). The race in Figure 7 occurs when
multiple clients concurrently access the same table.
To fix this race, an execution filter can add a lock ac-
quisition at line 13 in open_table() and a lock re-
lease at line 18 in close_table(). To safely install
this filter, however, the quiescence of open_table ()
and close_table() is not enough, because a thread
may still be running at line 8 and cause a double-unlock
error. An alternative fix is to add the lock acquisition and
release in function handle_client (), but this func-
tion hardly quiesces because of the busy loop (line 3-10)
and the blocking call recv ().
LOOM solves the unsafe state program using an algo-
rithm termed evacuation that can proactively quiesce ar-
bitrary code regions. From a high level, this algorithm
takes a filter and computes a set of unsafe program lo-
cations that may interfere with the filter. It does so con-
servatively to avoid marking an unsafe location as safe.
Then, it “evacuates” threads out of the unsafe locations
and blocks them at safe program location. After that, it
installs the filter and resumes the threads.
4.1 Computing Unsafe Program Locations
LOoM uses slightly different methods to compute the un-
safe program locations for mutual exclusion and for ex-
ecution order constraints. To compute unsafe program
locations for mutual exclusion constraints, LOOM per-
forms a static reachability analysis on the interprocedu-
ral control flow graph (ICFG) of an application. An
ICFG connects each function’s control flow graphs by
following function calls and returns. Figure 8a shows
the ICFG for the code in Figure 7. We say statement s;
reaches 89 or reachable(s1, 82) if there is a path from s;
to sz on the ICFG. For example, the statement at line 13
reaches the statement at line 8 in Figure 7.
Given an execution filter f with mutual exclusion
constraint r) <> rg <> <> Tn, LOOM in-
Unsafe to update Safe to update
ryt fy)
PY sl Install
La
Updated
\\\
ra /
ei
Loom Loom
Update Update
Engine Engine
Figure 9: Evacuation. Curved lines represent application
threads, solid triangles (in black) represents the threads’
program counters (PC), and solid stripes (in red) repre-
sents an unsafe code region.
cludes any statement s potentially inside one of the re-
gions in unsafe(f), the set of unsafe program loca-
tions for filter f. Specifically, unsafe(f) is the set
of statements s such that {reachable(r;.entries, s) A
reachable(s,r;.exits)} for i € [1,n], where r;.entries
are the entry statements to region r; and r;.exzts are the
exit statements.
LOOM computes unsafe program locations for an ex-
ecution order constraint by first deriving code regions
from the constraint, then reusing the method for mutual
exclusion to compute unsafe program locations. Specif-
ically, given e; > €2 > ... > €n, LOOM first computes
a dominator statement sq such that sg dominates all e;
(i.e., Sq is on every path from the program start to e;); it
then computes unsafe(f) as the set of statements inside
each { sq; e;} region.
Since e; may be in different threads, LOOM aug-
ments the ICFG of an application into thread interpro-
cedural control flow graph (TICFG) by adding edges
for thread creation and thread join statements. Cur-
rently our analysis constructs the TICFG by treating
each pthread_create (func) statement as a func-
tion call to func (): it adds an edge from the statement
to the entry of func () and a thread join edge from the
exit of func () to the statement.
4.2 Controlling Application Threads
LOOM needs to control application threads to pause and
resume them. It does so using a read-write lock called the
update lock. To live update an application, LOOM grabs
this lock in write mode, performs the update, and releases
this lock. To control application threads, LOOM’s com-
piler plugin instruments the application so that the ap-
plication threads hold this lock in read mode in normal
operation and check for an update once in a while by re-
leasing and re-grabbing this lock.
LOOM carefully places update-checks inside an appli-
cation to reduce the overhead and ensure a timely update.
Figure 8b shows the placement of these update-checks.
LOOM needs no update-checks inside straight-line code
with no blocking calls because such code can complete
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI’10) 141
142
( 3: entry of \
\handle_client/
5: ret =reed.) | \
Y
( 3: entry of
\handle_client /
3: entry of >
\handle_client /
tancleelen
<<switch?—
5: ret = recv(...)
after_blocking();
before_blocking();
Seana
—— / |before_blocking();
Y _ Be ( 13: entry of \
<6: ret<=0 \ open_table_/
Nj . | skipped
(15: exit of > Ny
y
Y 6: ret<=0
7: call open_table
pone table 7
7: call open_table
Ad
... /1 do real work = _ |
(/ 17: entry of >
¥
... // do real work
\close_table_/
y
9: call close_table ———
y Skipped
v
9: call close_table
(19: exit of >
\ close_table_/
(11: exit of >)
\handle_client/
(a) ICFG of the code in Figure 7.
(11: exit of >)
\handle_client /
(b) After update-checks inserted.
———
before_blocking();|_ / __slot();
5: ret = recv(...) \f 5: ret = recv(,..)
after_blocking(); \ slot();
Y | after_blocking();
Y <6: ret<=0~ cycle_check(); y Y
cycle_check(); / N F 6: ret<=0 >
/ | <<switch? — YN *
| | 7: call open_table slot(); \
| 1 | 7: call open_table |
{ ¥
||... Ido real work || slot(); |
\ T ) \ .. do real work |
\ v
\ 9: call close_table}| / \ slot(); |
J) \ — \ 9: call close_table }
Bio \ slot();
ee a Hs
se
(11: exit of ~
\handle_client /
(c) After basic blocks cloned.
Figure 8: Static transformations that LOOM does for safe and fast live update. Subfigure (a) shows the ICFG of the
code in Figure 7; (b) shows the resulting CFG of function process_client () after the instrumentation to control
application threads (84); (c) shows the final CFG of function process_client () after basic block cloning (85).
quickly. LOOM places one update-check for each cycle
in the control flow graph, including loops and recursive
function call chains, so that an application thread cycling
in one of these cycles can check for an update at least
once each iteration. Currently LOOM instruments the
backedge of a loop and an arbitrary function entry in a
recursive function cycle. LOOM does not instrument ev-
ery function entry because doing so is costly.
LOoM also instruments an application to release the
update lock before a blocking call and re-grab it af-
ter the call, so that an application thread blocking on
the call does not delay an update. For the example in
Figure 7, LOOM can perform the update despite some
threads blocking in recv(). LOOM instruments only
the “leaf-level” blocking calls. That is, if foo() calls
bar() and bar () is blocking, LOOM instruments the
calls to bar (), but not the calls to foo(). Currently
LOOM conservatively considers calls to external func-
tions (i.e., functions without source), except Math library
functions, as blocking to save user annotation effort.
4.3 Pausing at Safe Program Locations
Besides the update lock, LOOM uses additional syn-
chronization variables to ensure that application threads
pause at safe locations. LOOM assigns a wait flag for
each backedge of a loop and the chosen function entry
of a recursive call cycle. To enable/disable pausing at a
safe/unsafe location, LOOM sets/clears the correspond-
ing flag. The instrumentation code for each CFG cycle
(left of Figure 10) checks for an update only when the
corresponding wait flag is set. These wait flags allow ap-
plication threads at unsafe program locations to run until
they reach safe program locations, effectively evacuating
the unsafe program locations.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
// inserted at CFG cycle
void cycle_check() {
if(wait[stmt_id]) {
read_unlock(&update);
while(wait[stmt_id]); }
read_lock(&update); // inserted after blocking call
} void after_blocking() {
} read_lock(&update) ;
atomic_dec(&counter[callsite_id]);
}
Figure 10: Instrumentation to pause application threads.
// inserted before blocking call
void before_blocking() {
atomic_inc(&counter[callsite_id]);
read_unlock(&update);
Note that the statement “if (wait [stmt_id])” in
Figure 10 greatly improves LOOM’s performance. With
this statement, application threads need not always re-
lease and re-grab the update lock which can be costly,
and hardware cache and branch prediction can effectively
hide the overhead of checking these flags. This technique
speeds up LOOM significantly (§6) because wait flags are
almost always 0 with read accesses.
LOOM cannot use the wait-flag technique to skip a
blocking function call because doing so changes the ap-
plication semantics. Instead, LOOM assigns a counter to
each blocking callsite to track how many threads are at
the callsites (right of Figure 10). LOOM uses a counter
instead of a binary flag because multiple threads may be
doing the same call.
Now that LOOM’s instrumentation is in place, Fig-
ure 11 shows LOOM’s evacuation method which runs
within LOOM’s live update engine. This method first sets
the wait flags for safe backedges. It then grabs the update
lock in write mode, which pauses all application threads.
It then examines the counters of unsafe callsites and if
any counter is positive, it releases the update lock and
retries, so that the thread blocked at unsafe callsites can
USENIX Association
USENIX Association
volatile int wait{NBACKEDGE] = {0};
volatile int counter[NCALLSITE] = {0};
rwlock_t update;
void evacuate() {
for each B in safe backedges
wait[B] = 1; / turn on wait flags
retry:
write_lock(&update); // pause app threads
for each C in unsafe callsites
if(counter[C]) { // threads paused at unsafe callsites
write_unlock(&update);
goto retry;
... / update
for each B in safe backedges
wait[B] = 0; / turn off wait flags
write_unlock(&update); // resume app threads
}
Figure 11: Pseudo code of the evacuation algorithm.
wake up and advance to safe locations. Next, it updates
the application (85), clears the wait flags, and releases
the update lock.
4.4 Correctness Discussion
We briefly discuss the correctness of our evacuation al-
gorithm in this subsection; for a complete proof, please
refer to our technical report [53].
In program analysis terms, our reachability analysis
(84.1) is interprocedural and flow-sensitive. We use
a crude pointer analysis to discover thread functions,
thread join sites, and function pointer targets. We could
have refined our analysis to improve precision, but we
find it sufficient to compute unsafe locations for all eval-
uated races because (1) our analysis is sound and never
marks an unsafe location safe and (2) execution filters
are quite small and slight imprecision does not matter. In
the worst case, if our analysis turns out too imprecise for
some filters, the flexibility of LOOM allows developers
to easily adjust their filters to pass the safety analysis.
Server programs frequently use thread pools, creat-
ing problems for our reachability analysis. Specifically,
these servers tend to create a fixed set of threads dur-
ing initialization, then reuse them for independent re-
quests. If we compute dominators using the creation sites
of these threads, we would find that dominators only run
during server initialization. Fortunately, we can anno-
tate the reuse of a thread as a special thread creation site,
so that our algorithm computes correct dominators. In
our experiments, we did not (and need not) annotate any
thread reuse.
Our reachability analysis gives correct results de-
spite compiler reordering. In order to pause application
threads at safe locations, our reachability analysis returns
only the set of unsafe backedges and external callsites.
These locations are instrumented by LOOM; this instru-
mentation acts as barriers and prevents compilers from
void slot(int stmt_id) {
op-list = operations[stmt_id];
foreach op in op_list
do op;
Figure 12: Slot function.
reordering instructions across them.
The synchronization between the instrumentation in
Figure 10 and the evacuation algorithm in Figure 11 is
correct under two conditions: (1) read and write to wait
flags are atomic and (2) the operations to the update lock
contain correct memory barriers that prevent hardware
reordering. Currently we implement wait flags using
aligned integers; our update lock operations use atomic
operations similar to the Linux kernel’s rw-spinlock.
Thus, our evacuation algorithm works correctly on X86
and AMD64 which do not reorder instructions across
atomic instructions. We expect our algorithm to work on
other commodity hardware that also provides this guar-
antee. To cope with more relaxed hardware (e.g., , Al-
pha), we can augment these operations with full barriers.
5 Hybrid Instrumentation
Most previous live update systems update binaries by
compiling updated functions and redirecting old func-
tions to the new function binaries using a table or jump
instructions. This approach requires source patches to
generate the updates, thus it has the limitations described
in §1. Moreover, this approach pays the overhead of po-
sition independent code (PIC) because application func-
tions must be compiled as PIC for live update. It also suf-
fers the aforementioned function quiescence problem.”
Another alternative is to use general-purpose binary
instrumentation tools such as vx32 [20], Pin [34] and Dy-
namoRIO [14], but they tend to incur significant runtime
overhead just to run their frameworks alone. For exam-
ple, Pin has been reported to incur 199% overhead [34],
and we observed 10 times slowdown on Apache with a
CPU-bound workload (86).
Loom’s hybrid instrumentation engine reduces run-
time overhead by combining static and dynamic instru-
mentation. This engine statically transforms an applica-
tion’s binary to anticipate dynamic updates. The static
transformation pre-pads, before each program location,
a slot function which interprets the updates to this pro-
gram location at runtime. Figure 12 shows the pseudo
code of this function. It iterates though a list of synchro-
nization operations assigned to the current statement and
performs each. To update a program location at runtime,
LOOM simply modifies the corresponding operation list.
Inserting the slot function at every statement incurs
?The function quiescence problem can be addressed by transform-
ing loop bodies into functions [38, 39] but only if the CFGs are re-
ducible [23].
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) —-143
144
Race ID Description
MySQL-791 Calls to close () and open () to flush log file
are not atomic. Figure 2 shows the code.
MySQL-169 Table update and log write in mysql_delete ()
are not atomic.
MySQL-644 Calls to prepare() and optimize ()
mysql_select () are not atomic.
Apache-21287
atomic.
Apache-25520
sulting in corrupted logs or crashes.
PBZip2 Variable fifo is used in one thread after being
freed by another. Figure 4 shows the code.
SPLASH2-fft Variable finishtime and initdonetime
are read before assigned the correct values.
SPLASH2-lu Variable rf is read before assigned the correct
value.
SPLASH2-barnes
correct value.
Table 3: All races used in evaluation. We identify
races in MySQL and Apache as “(application name) —
(Bugzilla #)”, the only race in PBZip2 “PB Zip2”, and races
in SPLASH2 “SPLAS H2 — (benchmark name)”.
high runtime overhead and hinders compiler optimiza-
tion. LOOM solves this problem using a basic block
cloning idea [29]. LOOM keeps two versions of each
basic block in the application binary, an originally com-
piled version that is optimized, and a hot backup that is
unoptimized and padded for live update. To update a ba-
sic block at runtime, LOOM simply updates the backup
and switches the execution to the backup by flipping a
switch flag.
LOOM instruments only function entries and loop
backedges to check the switch flags because doing so for
each basic block is expensive. Similar to the wait flags in
(84), the switch flags are almost always 0, so that hard-
ware cache and branch predication can effectively hide
the overhead of checking them. This technique makes
live-update-ready applications run as fast as the origi-
nal application during normal operations (86). Figure 8c
shows the final results after all LOOM transformations.
Note that the accesses to switch flags are correctly pro-
tected by the update lock. An application checks the
switch flag when holding the update lock in read mode,
and the update engine sets the switch flag when holding
the update lock in write mode.
6 Evaluation
We implemented LOOM in Linux. It consists of 4,852
lines of C++ code, with 1,888 lines for the LLVM com-
piler plugin, 2,349 lines for the live-update engine, and
615 lines for the controller.
We evaluated LOOM on nine real races from a diverse
set of applications, ranging from two server applications
MySQL [5] and Apache [11], to one desktop application
PBZip2 [6], to three scientific applications fft, lu, and
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
Reference count decrement and checking are not
Threads write to same log buffer concurrently, re-
Variable t rackt ime is read before assigned the
barnes in SPLASH2 [7]. Table 3 lists all nine races.
Our race selection criteria is simple: (1) they are exten-
sively used in previous studies [31, 42, 43] and (2) the
application can be compiled by LLVM and the race can
be reproduced on our main evaluation machine, a 2.66
GHz Intel quad-core machine with 4 GB memory run-
ning 32-bit Linux 2.6.24.
We used the following workloads in our experi-
ments. For MySQL, we used SysBench [8] (advanced
transaction workload), which randomly selects, updates,
deletes, and inserts database records. For Apache, we
used ApacheBench [1], which repeatedly downloads a
webpage. Both benchmarks are multithreaded and used
by the server developers. We made both SysBench and
ApacheBench CPU bound by fitting the database or web
contents within memory; we also ran both the client
and the server on the same machine, to avoid mask-
ing LOOM’s overhead with the network overhead. Un-
less otherwise specified, we ran 16 worker threads for
MySQL and Apache because they performed best with
8-16 threads. We ran four worker threads for PBZip2 and
SPLASH2 applications because they are CPU-intensive
and our evaluation machine has four cores.
We measured throughput (TPUT) and response time
(RESP) for server applications and overall execution
time for other applications. We report LOOM’s relative
overhead, the smaller the better. We compiled the appli-
cations down to x86 instructions using llvym-—gcc -O2
and LLVM’s bitcode compiler 11c. For all the perfor-
mance numbers reported, we repeated the experiment 50
times and take the average.
We focus our evaluation on five dimensions:
1. Overhead. Does LOOM incur low overhead?
2. Scalability. Does LOOM scale well as the number of
application threads increases?
3. Reliability. Can LOOM be used to fix the races listed
in Table 3? What are the performance and reliability
tradeoffs of execution filters?
4. Availability. Does LOOM severely degrade applica-
tion availability when execution filters are installed?
5. Timeliness. Can LOOM install fixes in a timely way?
6.1 Overhead
Figure 13 shows the performance overhead of LOOM
during the normal operations of the applications. We
also show the overhead of bare Pin for reference. LOOM
incurs little overhead for Apache and SPLASH2 bench-
marks. It increases MySQL’s response time by 4.11%
and degrades its throughput by 3.76%. In contrast, Pin
incurs higher overhead for all applications evaluated, es-
3We include applications that do not need live update for two rea-
sons. First, as discussed in §1, LOOM can provide quick workarounds
for these applications as well. Second, we use them to measure LOOM’s
overhead and scalability.
USENIX Association
USENIX Association
Optimization Effects on Apache
= 120
xs unopt &
S 100 cloning &
° 80 wait-flag
5 : inlining &
® 60 S
oO
5 40
8 20
p=
o 0) RNY TRE |
. we | TPUT RESP
Optimization Effects on SPLASH2
<S 2000
os: unop'
S cloning
2 1500+ wait-flag
So inlining fcc:
oO
oO
ao 1000
c
oO
3 500
2 ® er
g o 1
6 0 wv M..
fft barnes
Figure 14: Effects of LOOM’s optimizations. Label unopt represents the versions with no optimizations; cloning represents the
version with basic block cloning (§5); wait-flag represents the version with statement “if (wait [stmt_id] )” added (84.2); and
inlining indicates the version with all LOOM instrumentation inlined into the applications.
Overhead (%)
1065.39
Figure 13: LOOM’s relative overhead during normal opera-
tion. Smaller numbers are better. We show Pin’s overhead for
reference. Some Pin bars are broken.
pecially for Apache and MySQL.
We also evaluated how the optimizations we do reduce
LOOM’s overhead. Figure 14 shows the effects of these
optimizations. Both cloning and wait-flag are very ef-
fective at reducing overhead. Cloning reduces LOOM’s
response-time overhead on Apache from 100% to 17%.
It also reduces LOOM’s overhead on fft from 15 times to
8 times. Wait-flag actually makes Apache run faster than
the original version. Inlining does not help the servers
much, but it does help for SPLASH2 applications.
6.2 Scalability
LOOM synchronizes with application threads via a read-
write lock. Thus, one concern is, can LOOM scale well
as the number of application threads increases? To evalu-
ate LOOM’s scalability, we ran Apache and MySQL with
LOOM on a 48-core machine with four 1.9 GHz 12-core
AMD CPUs and 64 GB memory running 64-bit Linux
2.6.24. In each experiment, we pinned the benchmark to
one CPU and the server to the other three to avoid unnec-
essary CPU contention between them.
Figure 15 shows LOOM’s relative overhead vs. the
number of application threads for Apache and MySQL.
Race ID Mutual Unilateral
Events TPUT RESP Events TPUT RESP
MySQL-169 2 0.14% 0.15% 1 3.28% 3.37%
MySQL-644 4 0.22% 0.20% 32.58% 48.34%
MySQL-791 4 0.23% 0.32% 0.33% 0.48%
Apache-21287 16
Apache-25520 1
-0.02% -0.03%
0.52% 0.55%
54.03% 118.16%
86.04% 637.03%
FPNN A
Table 4: Execution filter stats for atomicity errors. Col-
umn Events counts the number of events in each filter.
Race ID Events Overhead
PBZip2 6 1.26%
SPLASH?2-fft 6 0.08%
SPLASH?2-lu 2 1.68%
SPLASH2-barnes 2 1.99%
Table 5: Execution filter stats for order errors.
LOoM scales well with the number of threads. Its rela-
tive overhead varies only slightly. Even with 32 server
threads, the overhead for Apache is less than 3%, and the
overhead for MySQL is less than 12%.
Our initial MySQL overhead was around 16%.
We analyzed the execution counts of the LOOM-
inserted functions and immediately identified two
update-check sites (cycle_check () calls) that exe-
cuted exceedingly many times. These update-check
sites are in MySQL functions ptr_compare_1 and
Field varstring::val_str. The first function
compares two strings, and the second copies one string to
another. Each function has a loop with a few statements
and no function calls. Such tight loops cause higher over-
head for LOOM, but rarely need to be updated. We thus
disabled the update-check sites in these two functions,
which reduced the overhead of MySQL down to 12%.
This optimization can be easily automated using static or
dynamic analysis, which we leave for future work.
6.3 Reliability
LOOM can be used to fix all races evaluated. (We verified
this result by manually inspecting the application binary.)
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) —:145
146
Apache's Scalability
4
sz ? ‘
co 0 Bq =
: 2 We
= -4
6 +6
-8 TPUT
10 RESP
1 2 4 8 16 32
Number of threads
MySQL’s Scalability
15
TPUT
RESP
se 10
3
o 5
£
$
oO 0
5
1 2 4 8 16 32
Number of threads
Figure 15: LOOM’s relative overhead vs. the number of application threads.
Table 4 shows the statistics for the execution filters that
fix atomicity errors. Table 5 shows the statistics for the
execution filters that fix order errors.
In all cases, we can fix the race using multiple execu-
tion filters, demonstrating the flexibility of Loom. (The
filters for MySQL-791 are shown in Figure 3.) We only
show the statistics of one execution filter of each con-
straint type; other filters of the same type are similar. Our
results show that the filters are fairly small, 3.79 events
on average and no more than 16 events, demonstrating
the ease of use of LOOM. Most filters incur only a small
overhead on top of LOOM. Unilateral filters tend to be
slightly smaller than mutual exclusion filters, but they
can be expensive sometimes. They incur little overhead
for two of the MySQL bugs because the code regions
protected by the filters rarely run.
These different reliability and performance overheads
present an interesting tradeoff to developers. For ex-
ample, users can choose to install a unilateral filter for
immediate protection, then atomically replace it with
a faster mutual exclusion filter. Moreover, a user can
choose an “expensive” filter as long as their workload
is compatible with the filter.
6.4 Availability
We show that LOOM can improve server availability
by comparing LOOM to the restart-based software up-
date approach. We restarted a server by running its
startup script under /etc/init.d. We chose two
races, MySQL-791 and Apache-25520, and measured
how software updates (conventional or with LOOM)
might degrade performance. Note this comparison fa-
vors conventional updates because we only compare the
installation of the fix, but LOOM also makes it quick to
develop fixes. Figure 16 shows the comparison result.
Using the restart approach, Apache is unavailable for 4
seconds, and MySQL is unavailable for 2 seconds. More-
over, the restarts also cause Apache and MySQL to lose
their internal cache, leading to a ramp-up period after the
restart. In contrast, installing an filter using LOOM (at
second 5) does not degrade throughput for MySQL and
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
only degrades throughput slightly for Apache.
6.5 Timeliness
The more timely LOOM installs a filter, the quicker the
application is protected from the corresponding race.
This timeliness is critical for server applications because
malicious clients may exploit a known race and launch
attacks. In this subsection, we compare how timely
LOOM’s evacuation algorithm installs an aggressive filter
vs. an approach that passively waits for function quies-
cence. We chose Apache-25520 as the benchmark race.
We wrote a simple mutual exclusion filter that fixes the
race by making function ap_buffered_log_writer
a critical region. We then measured the latency from
the moment LOOM receives a filter to the moment
the filter is installed. We simulated a function quies-
cence approach by running LOOM without making any
wait_flag false, so that a thread can pause wher-
ever we insert update-checks. We used the same Sys-
Bench and ApacheBench workload. Our results show
that LOOM can install the filter within 368 ms. It spends
majority of the time waiting for threads to evacuate. In
contrast, an approach based on function quiescence fails
to install the filter in an hour, our experiment’s time limit.
7 Related Work
Live update Loom differs from previous live update
systems [10, 12, 15, 35, 38, 39, 51] in that it is explic-
itly designed for developers to quickly develop tempo-
rary workarounds to races. Moreover, it can automati-
cally ensure the safety of the workarounds. In contrast,
previous work focuses only on live update after a source
patch is available, thus it does not address the automatic-
safety and flexibility problems LOOM addresses.
The live update system closest to LOOM is
STUMP [38], which can live-update multithreaded appli-
cations written in C. Its prior version Ginseng [39] works
with single-threaded C applications. Both STUMP and
Ginseng have been shown to be able to apply arbitrary
source patches and update applications across major re-
leases. Unlike LOoM, both STUMP and Ginseng require
USENIX Association
USENIX Association
MySQL’s Availability
BZ 400 pe 2 i
oO NS, ra me
a ‘p e
= 300 \ g
2 \ | Loom ——
2 200 \ / Restart
oD a !
3 i
£ 100
<=
- \
0 1 1 2 b. 1 1 1 J
0 2 4 6 8 10 12 14
Time (s)
Apache’s Availability
20000
9 8 —1—. ta : oe
® 15000 \ j
- \ i Loom ——
a 10000 / Restart
o.
2 !
3 5000 ;
co 2D
- Z
0 di ag’
0 2 4 6 8 10 12 14
Time (s)
Figure 16: Throughput degradation for fixing races with LOOM vs. with conventional software update.
source modifications and rely on extensive user annota-
tions for safety because the safety of arbitrary live up-
dates has been proven undecidable [22].
A number of live update systems can update kernels
without reboots [12, 15, 35]. The most recent one,
Ksplice [12], constructs live updates from object code,
and does not require developer efforts to adapt existing
source patches. Unlike LOOM, Ksplice uses function
quiescence for safety, and is thus prone to the unsafe
state problem discussed in 84. Another kernel live up-
date system, DynAMOS [35], requires users to manu-
ally construct multiple versions of a function to update
non-quiescent functions. This technique is different from
basic block cloning (85): the former is manual and for
safety, whereas the later is automatic and for speed.
Error workaround and _ recovery We compare
LOOM to recent error workaround and recovery tools.
ClearView [44], ASSURE [50], and Failure-oblivious
computing can increase application availability by
letting them continue despite errors. Compared to
Loom, these systems are unsafe, and do not directly
deal with races. Rx [46] can safely recover from runtime
faults using application checkpoints and environment
modifications, but it does not fix errors because the
same error can re-appear. Vigilante [17] enables hosts
to collaboratively contain worms using self-verifiable
alerts. By automatically ensuring filter safety, LOOM
shares similar benefits.
Two recent systems, Dimmunix [26] and Gadara [52],
can fix deadlocks in legacy multithreaded programs.
Dimmunix extracts signatures from occurred deadlocks
(or starvations) and dynamically avoids them in future
executions. Gadara uses control theory to statically
transform a program into a deadlock-free program. Both
systems have been shown to work on real, large applica-
tions. They may possibly be adapted to fix races, albeit at
a coarser granularity because these systems control only
lock operations.
Kivati [16] automatically detects and prevents atom-
icity violations for production systems. It reduces per-
formance overhead by cleverly using hardware watch
points, but the limited number of watch points on com-
modity hardware means that Kivati cannot prevent all
atomicity violations. Nor does Kivati prevent execution
order violations. LOOM can be used to workaround these
errors missed by Kivati.
Program instrumentation frameworks Previous work
(3, 19, 40] can instrument programs with low runtime
overhead, but instrumentation has to be done at compile
time. Translation-based dynamic instrumentation frame-
works [14, 20, 34] can update programs at runtime but
incur high overhead. In particular, vx32 [20] is a novel
user-level sandbox that reduces overhead using segmen-
tation hardware; it can be used as an efficient dynamic
binary translator. Jump-based instrumentation frame-
works [24, 48] have low overhead but automatically en-
suring safety for them can be difficult due to low-level is-
sues such as position-dependent code, short instructions,
and locations of basic blocks.
One advantage of these instrumentation frameworks
over LOOM is that LOOM requires CFGs and symbol in-
formation to be distributed to user machines, thus it risks
leaking proprietary code information. However, this risk
is not a concern for open-source software. Moreover,
LOoM only mildly increases this risk because CFGs
can often be reconstructed from binaries, and companies
such as Microsoft already share symbol information [4].
The advantage of LOOM is that it combines static
and dynamic instrumentation, thus allowing arbitrary dy-
namic updates issued by execution filters with negligible
runtime overhead. LOOM borrows basic block cloning
from previous work by Liblit et al. [29], but their frame-
work is static only. This idea has also been used in other
systems (e.g., LIFT [45]).
Other related work Our work was inspired by many ob-
servations made by Lu ef al. [33]. Aspect-oriented pro-
gramming (AOP) allows developers to “weave” in syn-
chronizations into code [27, 30]. LOOM’s execution filter
language shares some similarity to AOP, and can be made
more expressive by incorporating more aspects. How-
ever, to the best of our knowledge, no existing AOP sys-
tems were designed to support race fixing at runtime. We
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) —-147
148
view the large body of race detection and diagnosis work
(e.g., [31, 32, 37, 42, 47, 49, 54]) as complimentary to
our work and LOOM can be used to fix errors detected
and isolated by these tools.
8 Conclusion
We have presented LOOM, a live-workaround system de-
signed to quickly and safely fix application races at run-
time. Its flexible language allows developers to write
concise execution filters to declare their synchronization
intents on code. Its evacuation algorithm automatically
ensures the safety of execution filters and their installa-
tion/removal processes. It uses hybrid instrumentation to
reduce its performance overhead during the normal oper-
ations of applications. We have evaluated LOOM on nine
real races from a diverse set of applications. Our results
show that LOOM is fast, scalable, and easy to use. It can
safely fix all evaluated races in a timely manner, thereby
increasing application availability.
LOoM demonstrates that live-workaround systems can
increase application availability with little performance
overhead. In our future work, we plan to extend this idea
to other classes of errors (e.g., security vulnerabilities).
Acknowledgement
We thank Cristian Cadar, Jason Nieh, Jinyang Li,
Michael Kester, Xiaowei Yang, Vijayan Prabhakaran
(our shepherd), and the anonymous reviewers for their
tremendous feedback and comments, which have sub-
stantially improved the content and presentation of this
paper. We thank Shan Lu for providing many of the races
used in our evaluation. We thank Jane-Ellen Long for
time management.
This work was supported by the National Science
Foundation (NSF) through Contract CNS-1012633 and
CNS-0905246 and the United States Air Force Re-
search Laboratory (AFRL) through Contract FA8650-
10-C-7024 and FA8750-10-2-0253. Opinions, findings,
conclusions, and recommendations expressed in this ma-
terial are those of the authors and do not necessarily re-
flect the views of the US Government.
References
1] ab - Apache HTTP server benchmarking tool. http://
httpd.apache.org/docs/2.2/programs/ab.html.
2] The K42 Project.
K42/.
http://www.research. ibm.com/
3] The LLVM Compiler Framework. http://llvm.org.
4] Download windows symbol packages. http://www.
microsoft.com/whdc/devtools/debugging/
debugstart .mspx.
5] MySQL Database. http: //www.mysql.com/.
6] Parallel BZIP2 (PBZIP2).
pbzip2/.
7| Stanford Parallel Applications for Shared Memory (SPLASH).
http://compression.ca/
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
[8]
[9]
[10]
(11)
[12]
{13]
[14]
{15]
[16]
[17]
[18]
[19]
[20]
[21]
[22]
[23]
[24]
[25]
http://www-flash.stanford.edu/apps/SPLASH/.
SysBench: a system performance benchmark.
sysbench.sourceforge.net.
http://
G. Altekar and I. Stoica. ODR: output-deterministic replay for
multicore debugging. In Proceedings of the 22nd ACM Sympo-
sium on Operating Systems Principles (SOSP ’09), pages 193-
206, 2009.
G. Altekar, I. Bagrak, P. Burstein, and A. Schultz. OPUS: on-
line patches and updates for security. In Proceedings of the 14th
USENIX Security Symposium, 2005.
Apache Web Server. http: //www.apache.org.
J. Arnold and F. M. Kaashoek. Ksplice: Automatic rebootless
kernel updates. In Proceedings of the 4th ACM European Con-
ference on Computer Systems (EUROSYS ’09), pages 187-198,
Apr. 2009.
A. Baumann, G. Heiser, J. Appavoo, D. Da Silva, O. Krieger,
R. W. Wisniewski, and J. Kerr. Providing dynamic update in an
operating system. In Proceedings of the USENIX Annual Techni-
cal Conference (USENIX ’05), pages 32-32, 2005.
D. L. Bruening.
runtime code manipulation.
Amarasinghe, Saman.
H. Chen, R. Chen, F. Zhang, B. Zang, and P.-C. Yew. Live updat-
ing operating systems using virtualization. In Proceedings of the
2nd International Conference on Virtual Execution Environments
(VEE ’06), pages 35-44, 2006.
Efficient, transparent, and comprehensive
PhD thesis, 2004. Supervisor-
L. Chew and D. Lie. Kivati: fast detection and prevention of
atomicity violations. In EuroSys ’10: Proceedings of the 5th Eu-
ropean conference on Computer systems, pages 307-320, 2010.
M. Costa, J. Crowcroft, M. Castro, A. Rowstron, L. Zhou,
L. Zhang, and P. Barham. Vigilante: end-to-end containment of
internet worms. In Proceedings of the 20th ACM Symposium on
Operating Systems Principles (SOSP ’05), pages 133-147, 2005.
G. W. Dunlap, D. G. Lucchetti, M. A. Fetterman, and P. M. Chen.
Execution replay of multiprocessor virtual machines. In Proceed-
ings of the 4th International Conference on Virtual Execution En-
vironments (VEE ’08), pages 121-130, 2008.
D. Engler, B. Chelf, A. Chou, and S. Hallem. Checking system
rules using system-specific, programmer-written compiler exten-
sions. In Proceedings of the Fourth Symposium on Operating
Systems Design and Implementation (OSDI ’00), Sept. 2000.
B. Ford and R. Cox. Vx32: lightweight user-level sandboxing
on the x86. In Proceedings of the USENIX Annual Technical
Conference (USENIX ’08), pages 293-306, 2008.
S. Gilmore and C. Walton. Dynamic ML without dynamic types.
Technical report, Lab. for the Foundations of Computer Science,
University of Edinburgh, 1997.
D. Gupta, P. Jalote, and G. Barua. A formal framework for on-
line software version change. JEEE Transactions on Software
Engineering, 22(2):120-131, 1996.
M. S. Hecht and J. D. Ullman. Characterizations of reducible
flow graphs. Journal of the ACM, 21(3):367-375, 1974.
G. Hunt and D. Brubacher. Detours: Binary interception of win32
functions. In In Proceedings of the 3rd USENIX Windows NT
Symposium, pages 135-143, 1998.
P. Joshi, C.-S. Park, K. Sen, and M. Naik. A randomized dynamic
program analysis technique for detecting real deadlocks. In Pro-
ceedings of the ACM SIGPLAN 2009 Conference on Program-
ming Language Design and Implementation (PLDI ’09), pages
110-120, June 2009.
USENIX Association
[26]
[27]
[28]
[29]
[30]
[31]
[32]
[33]
[34]
[35]
[36]
[37]
[38]
[39]
[40]
USENIX Association
H. Jula, D. Tralamazza, Z. Cristian, and C. George. Deadlock im-
munity: Enabling systems to defend against deadlocks. In Pro-
ceedings of the Eighth Symposium on Operating Systems Design
and Implementation (OSDI ’08), pages 295-308, Dec. 2008.
G. Kiczales, J. Lamping, A. Mendhekar, C. Maeda, C. Lopes,
J. marc Loingtier, and J. Irwin. Aspect-oriented programming. In
ECOOP, 1997.
O. Laadan, N. Viennot, and J. Nieh. Transparent, lightweight
application execution replay on commodity multiprocessor op-
erating systems. In Proceedings of the 2010 ACM SIGMETRICS
Conference on Measurement and Modeling of Computer Systems,
2010.
B. Liblit, A. Aiken, A. X. Zheng, and M. I. Jordan. Bug isola-
tion via remote program sampling. In PLDI ’03: Proceedings of
the ACM SIGPLAN 2003 conference on Programming language
design and implementation, pages 141-154, 2003.
D. Lohmann, W. Hofer, W. Schrder-Preikschat, J. Streicher, and
O. Spinczyk. CiAO: An aspect-oriented operating-system family
for resource-constrained embedded systems. In Proceedings of
the USENIX Annual Technical Conference (USENIX ’09), 2009.
S. Lu, J. Tucek, F. Qin, and Y. Zhou. Avio: Detecting atomic-
ity violations via access interleaving invariants. In Twelfth Inter-
national Conference on Architecture Support for Programming
Languages and Operating Systems (ASPLOS ’06), pages 37-48,
Oct. 2006.
S. Lu, S. Park, C. Hu, X. Ma, W. Jiang, Z. Li, R. A. Popa, and
Y. Zhou. Muvi: automatically inferring multi-variable access cor-
relations and detecting related semantic and concurrency bugs.
SIGOPS Oper. Syst. Rev., 41(6):103—116, 2007.
S. Lu, S. Park, E. Seo, and Y. Zhou. Learning from mistakes:
a comprehensive study on real world concurrency bug character-
istics. In ASPLOS XIII: Proceedings of the 13th international
conference on Architectural support for programming languages
and operating systems, pages 329-339, 2008.
C. Luk, R. Cohn, R. Muth, H. Patil, A. Klauser, G. Lowney,
S. Wallace, V. Reddi, and K. Hazelwood. Pin: building cus-
tomized program analysis tools with dynamic instrumentation.
In Proceedings of the ACM SIGPLAN 2005 Conference on Pro-
gramming Language Design and Implementation (PLDI ’05),
pages 190-200, 2005.
K. Makris and K. Ryu. Dynamic and adaptive updates of non-
quiescent subsystems in commodity operating system kernels. In
Proceedings of the 2nd ACM SIGOPS/EuroSys European Confer-
ence on Computer Systems 2007, page 340, 2007.
P. Montesinos, M. Hicks, S. T. King, and J. Torrellas. Capo:
a software-hardware interface for practical deterministic multi-
processor replay. In Fourteenth International Conference on Ar-
chitecture Support for Programming Languages and Operating
Systems (ASPLOS ’09), pages 73-84, 2009.
M. Musuvathi, S. Qadeer, T. Ball, G. Basler, P. A. Nainar, and
I. Neamtiu. Finding and reproducing heisenbugs in concurrent
programs. In Proceedings of the Eighth Symposium on Operating
Systems Design and Implementation (OSDI ’08), pages 267-280,
Dec. 2008.
I. Neamtiu and M. Hicks. Safe and timely dynamic updates for
multi-threaded programs. In Proceedings of the ACM SIGPLAN
2009 Conference on Programming Language Design and Imple-
mentation (PLDI ’09), pages 13-24, June 2009.
I. Neamtiu, M. Hicks, G. Stoyle, and M. Oriol. Practical dynamic
software updating for C. pages 72-83, June 2006.
G. C. Necula, S. McPeak, S. Rahul, and W. Weimer. CIL: Inter-
[41]
[42]
[43]
[44]
[45]
[46]
[47]
[48]
[49]
[50]
[51]
[52]
[53]
[54]
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
mediate Language and Tools for Analysis and Transformation of
C Programs. In Proceedings of Conference on Compilier Con-
struction, March 2002.
C.-S. Park and K. Sen. Randomized active atomicity violation de-
tection in concurrent programs. In Proceedings of the 16th ACM
SIGSOFT International Symposium on Foundations of Software
Engineering (SIGSOFT ’08/FSE-16), pages 135-145, Nov. 2008.
S. Park, S. Lu, and Y. Zhou. CTrigger: exposing atomicity vio-
lation bugs from their hiding places. In Fourteenth International
Conference on Architecture Support for Programming Languages
and Operating Systems (ASPLOS ’09), pages 25-36, Mar. 2009.
S. Park, Y. Zhou, W. Xiong, Z. Yin, R. Kaushik, K. H. Lee, and
S. Lu. PRES: probabilistic replay with execution sketching on
multiprocessors. In Proceedings of the 22nd ACM Symposium on
Operating Systems Principles (SOSP ’09), pages 177-192, 2009.
J. H. Perkins, S. Kim, S. Larsen, S. Amarasinghe, J. Bachrach,
M. Carbin, C. Pacheco, F. Sherwood, S. Sidiroglou, G. Sullivan,
W.-F. Wong, Y. Zibin, M. D. Ernst, and M. Rinard. Automatically
patching errors in deployed software. In Proceedings of the 22nd
ACM Symposium on Operating Systems Principles (SOSP ’09),
pages 87-102, 2009.
F. Qin, C. Wang, Z. Li, H.-s. Kim, Y. Zhou, and Y. Wu. Lift: A
low-overhead practical information flow tracking system for de-
tecting security attacks. In MICRO 39: Proceedings of the 39th
Annual IEEE/ACM International Symposium on Microarchitec-
ture, pages 135-148, 2006.
F. Qin, J. Tucek, Y. Zhou, and J. Sundaresan. Rx: Treating bugs
as allergies—a safe method to survive software failures. ACM
Trans. Comput. Syst., 25(3):7, 2007.
S. Savage, M. Burrows, G. Nelson, P. Sobalvarro, and T. E. An-
derson. Eraser: A dynamic data race detector for multithreaded
programming. ACM Transactions on Computer Systems, pages
391-411, Nov. 1997.
M. Schulz, D. Ahn, A. Bernat, B. R. de Supinski, S. Y. Ko,
G. Lee, and B. Rountree. Scalable dynamic binary instrumen-
tation for blue gene/l. SIGARCH Comput. Archit. News, 33(5):
9-14, 2005.
K. Sen. Race directed random testing of concurrent programs.
In Proceedings of the ACM SIGPLAN 2008 Conference on Pro-
gramming Language Design and Implementation (PLDI ’08),
pages 11-21, June 2008.
S. Sidiroglou, O. Laadan, C. Perez, N. Viennot, J. Nieh, and A. D.
Keromytis. ASSURE: automatic software self-healing using res-
cue points. In Fourteenth International Conference on Architec-
ture Support for Programming Languages and Operating Systems
(ASPLOS ’09), pages 37-48, 2009.
S. Subramanian, M. Hicks, and K. S. McKinley. Dynamic soft-
ware updates: a vm-centric approach. In Proceedings of the ACM
SIGPLAN 2009 Conference on Programming Language Design
and Implementation (PLDI ’09), pages 1-12, 2009.
Y. Wang, T. Kelly, M. Kudlur, S. Lafortune, and S. Mahlke.
Gadara: Dynamic deadlock avoidance for multithreaded pro-
grams. In Proceedings of the Eighth Symposium on Operating
Systems Design and Implementation (OSDI ’08), pages 281-294,
Dec. 2008.
J. Wu, H. Cui, and J. Yang. Bypassing races in live applications
with execution filters. Technical report, Columbia University.
Y. Yu, T. Rodeheffer, and W. Chen. RaceTrack: efficient detec-
tion of data race conditions via adaptive tracking. In SOSP ’05:
Proceedings of the twentieth ACM symposium on Operating sys-
tems principles, pages 221-234, 2005.
149
USENIX Association
Effective Data-Race Detection for the Kernel
John Erickson, Madanlal Musuvathi,
Sebastian Burckhardt, Kirk Olynyk
Microsoft Research
{jerick, madanm, sburckha, kirko}@microsoft.com
Abstract
Data races are an important class of concurrency errors where two threads erroneously access a shared memory loca-
tion without appropriate synchronization. This paper presents DataCollider, a lightweight and effective technique
for dynamically detecting data races in kernel modules. Unlike existing data-race detection techniques, DataCollider
is oblivious to the synchronization protocols (such as locking disciplines) the program uses to protect shared
memory accesses. This is particularly important for low-level kernel code that uses a myriad of complex architec-
ture/device specific synchronization mechanisms. To reduce the runtime overhead, DataCollider randomly samples
a small percentage of memory accesses as candidates for data-race detection. The key novelty of DataCollider is that
it uses breakpoint facilities already supported by many hardware architectures to achieve negligible runtime over-
heads. We have implemented DataCollider for the Windows 7 kernel and have found 25 confirmed erroneous data
races of which 12 have already been fixed.
1. Introduction
Concurrent systems are hard to design, arguably be-
cause of the difficulties of finding and fixing concur-
rency errors. Data races are an important class of con-
currency errors, where the program fails to use proper
synchronization when accessing shared data. The ef-
fects of an erroneous data race can range from immedi-
ate program crashes to silent lost updates and data cor-
ruptions that are hard to reproduce and debug.
Two memory accesses in a program are said to conflict
if they access the same memory location and at least
one of them is a write. A program contains a data race
if two conflicting accesses can occur concurrently. Fig-
ure | shows a variation of a data race we found in the
Windows kernel. The threads appear to be accessing
different fields. However, these bit-fields are mapped to
the same word by the compiler and the concurrent ac-
cesses result in a data race. In this case, an update to the
statistics field possibly hides an update to the status
field.
This paper presents DataCollider, a tool for dynamical-
ly detecting data races in kernel modules. DataCollider
is lightweight. It samples a small number of memory
accesses for data-race detection and uses code-
breakpoint and data-breakpoint’ facilities available in
modern hardware architectures to efficiently perform
this sampling. As a result, DataCollider has no runtime
overhead for non-sampled memory accesses allowing
the tool to run with negligible overheads for low sam-
pling rates.
We have implemented DataCollider for the 32-bit Win-
dows kernel running on the x86 architecture, and used it
to detect data races in the core kernel and several mod-
ules such as the filesystem, the networking stack, the
storage drivers, and a network file system. We have
found a total of 25 erroneous data races of which 12
have already been fixed at the time of writing. In our
experiments, the tool is able to find erroneous data rac-
es for sampling rates that incur runtime overheads of
less than 5%.
Researchers have proposed multitude of dynamic data-
race detectors [1,2,3,4,5,6,7] for user-mode programs.
In essence, these tools work by dynamically monitoring
the memory accesses and synchronizations performed
during a concurrent execution. As data races manifest
rarely at runtime, these tools attempt to infer conflicting
accesses that could have executed concurrently. The
tools differ in how they perform this inference, either
' Data breakpoints are also called hardware watchpoints.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI’10) =‘ 151
struct?
int status:4;
int pktRevd: 28;
} st;
Thread 1
Thread 2
st.status = 1; st.pktRcvd ++;
Figure 1: An example of data race. Even though the
threads appear to be modifying different variables in
the source code, the variables are bit fields mapping
to the same integer
using the happens-before [8] ordering induced by the
synchronization operations [4,5,6] or a lock-set based
reasoning [1] or a combination of the two [2,3,7]
There are several challenges in engineering a data-race
detection tool for the kernel based on previous ap-
proaches. First, the kernel-mode code operates at a low-
er concurrency abstraction than user-mode code, which
can rely on clean abstractions of threads and synchroni-
zations provided by the kernel. In the kernel, the same
thread context can execute code from a user-mode pro-
cess, a device interrupt service routine, or a deferred
procedure call (DPC). In addition, it is an onerous task
to understand the semantics of complex synchronization
primitives in order to infer the happens-before relation
or lock-sets. For instance, Windows supports more than
a dozen locks with different semantics on how the lock
holder synchronizes with hardware interrupts, the
scheduler, and the DPCs. It is also common for kernel
modules to roll-out custom implementations of syn-
chronization primitives.
Second, hardware-facing kernel modules need to syn-
chronize with hardware devices that concurrently modi-
fy device state and memory. It is important to design a
data-race detection tool that can find these otherwise
hard-to-find data races between the hardware and the
kernel.
Finally, existing dynamic data-race detectors add pro-
hibitive run-time overheads. It is not uncommon for
such tools to incur up to 200x slowdowns [9]. The
overhead is primarily due to the need to monitor and
process all memory and synchronization operations at
run time. Significant engineering effort in building da-
ta-race detectors goes in reducing the runtime overhead
and the associated memory and log management [9,3].
Replicating these efforts within the constraints of kernel
programming is an arduous, if not impossible, task.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
AtPeriodicIntervals() {
// determine k based on desired
// memory access sampling rate
repeat k times {
pc = RandomlyChosenMemoryAccess();
SetCodeBreakpoint( pe );
}
}
OnCodeBreakpoint( pe ) {
// disassemble the instruction at pc
(loc, size, isWrite) = disasm( pc );
DetectConflicts(loc, size, isWrite);
// set another code break point
pc = RandomlyChosenMemoryAccess ();
SetCodeBreakpoint( pce );
}
DetectConflicts( loc, size, isWrite) {
temp = read( loc, size );
if ( isWrite )
SetDataBreakpointRW( loc, size );
else
SetDataBreakpointW( loc, size );
delay();
ClearDataBreakpoint( loc, size );
temp’ = read( loc, size );
if( temp != temp’ ||
data breakpoint fired )
ReportDataRace( );
Figure 2: The basics of the DataCollider algo-
rithm. Right before a read or write access to shared
memory location, chosen at random, DataCollider
monitors for any concurrent accesses that conflict
with the current access.
Moreover, these tools rely on invasive instrumentation
techniques that are difficult to get right on low-level
kernel code.
DataCollider uses a different approach to overcome
these challenges. The crux of the algorithm is shown in
Figure 2. DataCollider samples a small number of
memory accesses at runtime by inserting code break-
points at randomly chosen memory access instructions.
When a code breakpoint fires, DataCollider detects data
races involving the sampled memory access for a small
time window. It simultaneously employs two strategies
USENIX Association
USENIX Association
to do so. First, DataCollider sets a data breakpoint to
trap conflicting accesses by other threads. To detect
conflicting writes performed by hardware devices and
by processors accessing the memory location through a
different virtual address, DataCollider use a repeated-
read strategy. It reads the value once before and once
after the delay. A change in value is an indication of a
conflicting write, and hence a data race.
The DataCollider algorithm has two features that make
it suitable for kernel data-race detection. First and
foremost, it is easy to implement. Barring some imple-
mentation details (Section 3), the entire algorithm is
shown in Figure 2. In addition, it is entirely oblivious to
the synchronization protocols used by the kernel and
the hardware, a welcome design point as DataCollider
does not have to understand the complex semantics of
kernel synchronization primitives.
When the DataCollider finds a data race through the
data-breakpoint strategy, it catches both threads “red-
handed,” as they are about to execute conflicting ac-
cesses. This greatly simplifies the debugging of data
race reports from DataCollider as the tool can collect
useful debugging information, such as the stack trace of
the racing threads along with their context information,
without incurring this overhead on non-sampled or non-
racy accesses.
Not all data races are erroneous. Such benign races in-
clude races that do not affect the program outcome,
such as updates to logging/debugging variables, and
races that affect the program outcome in a manner ac-
ceptable to the programmer, such as conflicting updates
to a low-fidelity counter. DataCollider uses a post-
processing phase that prunes and prioritizes the data-
race reports before showing them to the user. In our
experience with DataCollider, we have observed that
only around 10% percentage of data-race reports corre-
spond to real errors, making the post-processing step
absolutely crucial for the usability of the tool.
2. Background and Motivation
Shared memory multiprocessors are specifically built to
allow concurrent access to shared data. So why do data
races represent a problem at all?
The key motivation for data race detection is the empir-
ic fact that programmers most often use synchroniza-
tion to restrict accesses to shared memory. Data races
can thus be an indication of incorrect or insufficient
synchronization in the program. In addition, data races
can also reveal programming mistakes not directly re-
lated to concurrency, such as buffer overruns or use-
after-free, which indirectly result in inadvertent sharing
of memory.
Another important reason for avoiding data races is to
protect the program from the weak memory models of
the compiler and the hardware. Both the compiler and
hardware can reorder instructions and change the be-
havior of racy programs in complex and confusing
ways [10,11]. Even if a racy program works correctly
for the current compiler and hardware configuration, it
might fail on future configurations that implement more
aggressive memory-model relaxations.
While bugs caused by data races may of course be
found using more conventional testing approaches such
as stress testing, the latter often fails to provide actiona-
ble information to the programmer. Clearly, a data race
report including stack traces or data values (or even
better, including a core dump that is demonstrating the
actual data race) is easier to understand and fix than a
silent data corruption that leads to an obscure failure at
some later point during program execution.
2.1. Definition of Data Race
There is no “gold standard” for defining data races;
several researchers have used the term to mean different
things. For our definition, we consulted two respected
standards (Posix threads [12] and the drafts of the C++
and C memory model standards [11,10]) and general-
ized their definitions to account for the particularities of
kernel code. Our definition of data race is:
e Two operations that access main memory are
called conflicting if
o the physical memory they access is not
disjoint,
o at least one of them is a write, and
o they are not both synchronization access-
es.
e =A program has a data race if it can be executed on
a multiprocessor in such a way that two conflicting
memory accesses are performed simultaneously
(by processors or any other device).
This definition is a simplification of [11,10] insofar we
replaced the tricky notion of “not ordered before” with
the unambiguous “performed simultaneously” (which
refers to real time).
An important part of our definition is the distinction
between synchronization and data accesses. Clearly,
some memory accesses participate in perfectly desirable
races: for example, a mutex implementation may per-
form a “release” by storing the value 0 in a shared loca-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) —-153
154
tion, while another thread is performing an acquire and
reads the same memory location. However, this is not a
data race because we categorize both of these accesses
as synchronization accesses. Synchronization accesses
either involve hardware synchronization primitives
such as interlocked instructions or use volatile or atom-
ic annotations supported by the compiler.
Note that our definition is general enough to apply to
code running in the kernel, which poses some unique
problems not found in user-mode code. For example, in
some cases data races can be avoided by turning off
interrupts; also, processes can exhibit a data race when
accessing different virtual addresses that map to the
same physical address. We talk more about these topics
in Section 2.3.4.
2.2. Precision of Detection
Clearly, we would like data race detection tools to re-
port as many data races as possible without inundating
the user with false error reports. We use the following
terminology to discuss the precision and completeness
of data race detectors. A missed race is a data race that
the tool does not warn about. A benign data race is a
data race that does not adversely affect the behavior of
the program. Common examples of benign data races
include threads racing on updates to logging or statistics
variables and threads concurrently updating a shared
counter where the occasional incorrect update of the
counter does not affect the outcome of the program. On
the other hand, a false data race is an error report that
does not correspond to a data race in the program. Stat-
ic data-race detection techniques commonly produce
false data races due to their inherent inability to precise-
ly reason about program paths, aliased heap objects,
and function pointers. Dynamic data-race detectors can
report false data races if they do not identify or do not
understand the semantics of al/ the synchronizations
used by the program.
2.3. Related Work
Researchers have proposed and built a plethora of race
detection tools. We now discuss the major approaches
and implementation techniques appearing in related
work. We describe both happens-before-based and
lock-set-based tracking in some detail (Sections 2.3.2
and 2.3.3), before explaining why neither one is very
practical for data race detection in the kernel (Section
2.3.4).
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
2.3.1. Static vs. Dynamic
Data race detection can be broadly categorized into
static race detection [13,14,15,16,17], which typically
analyzes source or byte code without directly executing
the program, and dynamic race detection [1,2,3,4,5,6,7],
which instruments the program and monitors its execu-
tion online or offline.
Static race detectors have been successfully applied to
large code bases [13,14]. However, as they rely on ap-
proximate information, such as pointer aliasing, they
are prone to excessive false warnings. Some tools, es-
pecially those targeting large code bases, approach this
issue by filtering the reported warnings using heuristics
[13]. Such heuristics can successfully reduce the false
warnings to a tolerable level, but may unfortunately
also eliminate correct warnings and lead to missed rac-
es. Other tools, targeted towards highly motivated users
that wish to interactively prove absence of data races,
report all potential races to the user and rely on user-
supplied annotations that indicate synchronization dis-
ciplines [16,17].
Dynamic data race detectors are less prone to false
warnings than static techniques because they monitor
an actual execution of the program. However, they may
miss races because successful detection might require
an error-inducing input and/or an appropriate thread
schedule. Also, many dynamic detectors employ several
heuristics and approximations that can lead to false
alarms.
Dynamic data race detectors can be classified into cate-
gories based on whether they model a happens-before
relation [6,5,7] (see Section 2.3.2), lock sets [1] (see
Section 2.3.3), or both [2,18].
2.3.2. Happens-Before Tracking
Dynamic data race detectors do not just detect data rac-
es that actually took place (in the sense that the conflict-
ing accesses were truly simultaneous during the execu-
tion), but look for evidence that such a schedule would
have been possible for a slightly different timing.
Tracking a happens-before relation on program events
[8] is one way to infer the existence of a racy schedule.
This transitive relation is constructed by recording both
the ordering of events within a thread and the ordering
effects of synchronization operations across threads.
Once we can properly track the happens-before relation,
race detection is straightforward: For any two conflict-
ing accesses A and B, we simply check whether A hap-
pens-before B, or B happens-before A, or neither. If
USENIX Association
USENIX Association
neither, we know there exists a schedule where A and B
are simultaneous. If properly tracked, happens-before
does not lead to any false alarms. However, precise
tracking can be difficult to achieve in practice, as dis-
cussed in Section 2.3.4.
2.3.3. Lock Sets
When detecting races in programs that follow a strict
and consistent locking discipline, using a lock-set ap-
proach can provide some benefits. The basic idea is to
examine the lock set of each data access (that is, the set
of locks held during the access) and then to take for
each memory location the intersection of the lock sets
of all accesses to it. If that intersection is empty, the
variable is not consistently protected by any one lock
and a warning is issued.
The main limitation of the lock set approach is that it
does not check for true data races but for violations of a
specific locking discipline. Unfortunately, many appli-
cations (and in particular kernel code) use locking dis-
ciplines that are complex and use synchronization other
than locks.
Whenever a program departs from a simple locking
scheme in any of the above ways, lock-set-based race
detectors will be forced to either issue false warnings,
or to use heuristics to suppress these warnings. The
latter approach is common, especially in the form of
state machines that track the “sharing status” of a varia-
ble [1,3]. Such heuristics are necessarily imperfect
compromises, however (they always fail to suppress
some false warnings and always suppress some correct
warnings), and it is not clear how to tune them to be
useful for a wide range of applications.
2.3.4. Problems with Tracking Synchroni-
zations
Both lock-set and happens-before tracking require a
thorough understanding of the synchronization seman-
tics, lest they produce false alarms or miss races. There
are two fundamental difficulties we encountered when
trying to apply these techniques in the kernel:
e Abstractions that we take for granted in user mode
(such as threads) are no longer clearly defined in
kernel mode.
e The synchronization vocabulary of kernel code is
much richer and may include complicated se-
quences and ordering mechanisms provided by the
hardware.
For example, interrupts and interrupt handlers break the
thread abstraction, as the handler code may execute in a
thread context without being part of that thread in a
logical sense. Similar problems arise when a thread
calls into the kernel scheduler. The code executing in
the scheduler is not logically part of that same thread.
Another example illustrating the difficulty of modeling
synchronization inside the kernel are DMA accesses.
Such accesses are not executing inside a thread (in fact,
they are not even executing on a processor). Clearly,
traditional monitoring techniques have a problem be-
cause they cannot “instrument” the DMA access.
Similar case holds for interrupt processing. For exam-
ple, code may first write some data and then raise an
interrupt, and then the same data is read by an interrupt
handler. Lock sets would report a false alarm because
the data is not locked. But even happens-before tech-
niques are problematic, because they would need to
precisely track the causality between the instruction that
set the interrupt and the interrupt handler.
For these reasons, we decided to employ a design that
entirely avoids modeling the happens-before ordering
or lock-sets. As our results show, somewhat surprising-
ly, neither one is required to build an effective data race
detector.
2.3.5. Sampling to Reduce Overhead
To detect races, dynamic data race detectors need to
monitor the synchronizations and memory accesses
performed at runtime. This is typically done by instru-
menting the code and inserting extra monitoring code
for each data access. As the monitoring code executes
at every memory access, the overhead can be quite sub-
stantial.
One way to ameliorate this issue is to exclude some
data accesses from processing. Prior work has identi-
fied several promising strategies: adaptive sampling
that backs off hot locations [5] (the idea is that for such
locations the monitoring can be less frequent and still
detect races), or perform the full monitoring only for a
fixed fraction of the time [4] (the idea is that the proba-
bility of catching a race is roughly proportional to this
fraction multiplied by the number of times the race re-
peats). But these techniques still suffer from the cost of
sampling, performed at every memory access. DataCol-
lider avoids this problem by using hardware breakpoint
mechanisms.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) —-155
156
3. DataCollider Implementation
This section describes the implementation of the
DataCollider algorithm for the Windows kernel on the
x86 architecture. The implementation heavily uses the
code and data breakpoint mechanisms available on x86.
The techniques described in this paper can be extended
to other architectures and to user-mode code. But we
have not pursued this direction in this paper.
Figure 2 describes the basics of the DataCollider algo-
rithm. DataCollider uses the sampling algorithm, de-
scribed in Section 3.1, to process a small percentage of
memory accesses for data-race detection. For each of
the sampled memory accesses, DataCollider uses a con-
flict detection mechanism, described in Section 3.2, to
find data races involving the sampled access. After de-
tecting data races, DataCollider uses several heuristics,
described in Section 3.3, to prune benign data races.
3.1. The Sampling Algorithm
There are several challenges in designing a good sam-
pling algorithm for data-race detection. First, data races
involve two memory accesses both of which need to be
sampled to detect the race. If memory accesses are
sampled independently, then the probability of finding
the data race is a product of the individual sampling
probabilities. DataCollider avoids this multiplicative
effect by sampling the first access and using a data
breakpoint to trap the second access. This allows
DataCollider to be effective at low sampling rates.
Second, data races are rare events — most executed in-
structions do not result in a data race. The sampling
algorithm should weed out the small percentage of rac-
ing accesses from the majority of non-racing accesses.
The key intuition behind the sampling algorithm is that
if a program location is buggy and fails to use the right
synchronization when accessing shared data, then every
dynamic execution of that buggy code is likely to par-
ticipate in a data race. Accordingly, DataCollider per-
forms static sampling of program locations rather than
dynamic sampling of executed instructions. A static
sampler provides equal preference to rarely execution
instructions (which are likely to have bugs hidden in
them) and frequently executed instructions.
3.1.1. Static Sampling Using Code Break-
points
The static sampling algorithm works as follows. Given
a program binary, DataCollider disassembles the binary
to generate a sampling set consisting of all program
locations that access memory. The tool currently re-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
quires the debugging symbols of the program binary to
perform this disassembly. This requirement can be re-
laxed by using sophisticated disassemblers [19] in the
future.
DataCollider performs a simple static analysis to identi-
fy instructions that are guaranteed to only touch thread-
local stack locations and removes them from the sam-
pling set. Similarly, DataCollider removes synchroniz-
ing instructions from the sampling set by removing
instructions that accesses memory locations tagged as
“volatile” or those that use hardware synchronization
primitives, such as interlocked. This prevents DataCol-
lider from reporting races on synchronization variables.
However, DataCollider can still detect a data race be-
tween a synchronization access and a regular data ac-
cess, if the latter is in the sampling set.
DataCollider samples program locations from the sam-
pling set by inserting code breakpoints. The initial
breakpoints are set at a small number of program loca-
tions chosen uniformly randomly from the sampling set.
If and when a code breakpoint fires, DataCollider per-
forms conflict detection for the memory access at that
breakpoint. Then, DataCollider choses another program
location uniformly randomly from the sampling set and
sets a breakpoint at that location.
This algorithm uniformly samples all program locations
in the sampling set irrespective of the frequency with
which the program executes these locations. This is
because the choice of inserting a code breakpoint is
performed uniformly at random for all locations in the
sampling set. Over a period of time, the breakpoints
will tend to reside at rarely executed program locations,
increasing the likelihood that those locations are sam-
pled the next time they execute.
If DataCollider has information on which program loca-
tions are likely to participate in a race, either through
user annotations or through prior analysis [20] then the
tool can prioritize those locations by biasing their selec-
tion from the sampling set.
3.1.2. Controlling the Sampling Rate
While the program cannot affect the sampling distribu-
tion over program locations, the sampling rate is inti-
mately tied to how frequently the program executes
locations with a code breakpoint. In the worst case, if
all of the breakpoints are set on dead code, DataCollider
will stop performing data-race detection altogether. To
avoid this and to better control the sampling rate,
DataCollider periodically checks the number of break-
points fired every second, and adjusts the number of
USENIX Association
USENIX Association
breakpoints set in the program based on whether the
experienced sampling rate is higher or lower than the
target rate.
3.2. Conflict-Detection
As described in the previous section, DataCollider picks
a small percentage of memory accesses as likely candi-
dates for data-race detection. For these sampled access-
es, DataCollider pauses the current thread waiting to
see if another thread makes a conflicting access to the
same memory location. It uses two strategies: data
breakpoints and repeated-reads. DataCollider uses these
two strategies simultaneously as each complements the
weaknesses of the other.
3.2.1. Detecting Conflicts with Data Break-
points
Modern hardware architectures provide a facility to trap
when a processor reads or writes a particular memory
location. This is crucial for efficient support for data
breakpoints in debuggers. The x86 hardware supports
four data breakpoint registers. DataCollider uses them
to effectively monitor possible conflicting accesses to
the currently sampled access.
When the current access is a write, DataCollider in-
structs the processor to trap on a read or write to the
memory location. If the current access is a read,
DataCollider instructs the processor to trap only on a
write, as concurrent reads to the same location do not
conflict. If no conflicting accesses are detected,
DataCollider resumes the execution of the current
thread after clearing the data breakpoint registers.
Each processor has a separate data breakpoint register.
DataCollider uses an inter-processor interrupt to update
the break points on all processors atomically. This also
synchronizes multiple threads attempting to sample
different memory locations concurrently.
An x86 instruction can access variable sized memory.
For 8, 16, or 32-bit accesses, DataCollider sets a break-
point of the appropriate size. The x86 processor traps if
another instruction accesses a memory location that
overlaps with a given breakpoint. Luckily, this is pre-
cisely the semantics required for data-race detection.
For accesses that span more than 32 bits, DataCollider
uses more than one breakpoint up to the maximum
available of four. If DataCollider runs out of breakpoint
registers, it simply resorts to the repeated-read strategy
discussed below.
When a data breakpoint fires, DataCollider has success-
fully detected a race. More importantly, it has caught
the racing threads “red handed” — the two threads are at
the point of executing conflicting accesses to the same
memory location.
One particular shortcoming of data breakpoint support
in x86 that we had to work around was the fact that,
when paging is enabled, x86 performs the breakpoint
comparisons based on the virtual address and has no
mechanism to modify this behavior. Two concurrent
accesses to the same virtual addresses but different
physical addresses do not race. In Windows, most of
the kernel resides in the same address space with two
exceptions.
Kernel threads accessing the user address space cannot
conflict if the threads are executing in the context of
different processes. If a sampled access lies in the user
address space, DataCollider does not use breakpoints
and defaults to the repeated-read strategy.
Similarly, a range of kernel-address space, called ses-
sion memory, is mapped to different address spaces
based on the session the process belongs to. When a
sampled access lies in the session memory space,
DataCollider sets a data breakpoint but checks if the
conflicting accesses belong to the same session before
reporting the conflict to the user.
Finally, a data breakpoint will miss conflicts if a pro-
cessor uses a different virtual address mapped to the
same physical address as the sampled access. Similarly,
data breakpoints cannot detect conflicts arising from
hardware devices directly accessing memory. The re-
peated-read strategy discussed below covers all these
cases.
3.2.2. Detecting Conflicts with Repeated
Reads
The repeated-read strategy relies on a simple insight: if
a conflicting write changes the value of a memory loca-
tion, DataCollider can detect this by repeatedly reading
the memory location checking for value changes. An
obvious disadvantage of this approach is that it cannot
detect conflicting reads. Similarly, it cannot detect mul-
tiple conflicting writes the last of which writes the same
value as the initial value. Despite these shortcomings,
we have found this strategy to be very useful in prac-
tice. This is the first strategy we implemented (as it is
easier to implement than using data breakpoints) and
we were able to find several kernel bugs with this ap-
proach.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) —-157
158
However, repeated-reads strategy catches only one of
the two threads “red-handed.” This makes it harder to
debug data races, as one does not know which thread or
device was responsible for the conflicting write. This
was our prime motivation for using data breakpoints.
3.2.3. Inserting Delays
For a sampled memory access, DataCollider attempts to
detect a conflicting access to the same memory location
by delaying the thread for a short amount of time. For
DataCollider to be successful, this delay has to be long
enough for the conflicting access to occur. On the other
hand, delaying the thread for too long can be dangerous
especially if the thread holds some resource crucial for
the proper functioning of the entire system. In general,
it is impossible to predict how long to insert the delay.
After experimenting with many values, we chose the
following delay algorithm.
Depending on the IRQL (Interrupt Request Level) of
the executing thread, DataCollider delays the thread for
a preset maximum amount of time. At IRQLs higher
than the DISPATCH level (the level at which the kernel
scheduler operates), DataCollider does not insert any
delay. We considered inserting a small window of delay
at this level to identify possible data races between in-
terrupt service routines. But we did not expect that
DataCollider would be effective at short delays.
Threads running at the DISPATCH level cannot yield
the processor to another thread. As such, the delay is
simply a busy loop. We currently delay threads at this
level for a random amount of time less than | ms. For
lower IRQLs, DataCollider delays the thread for a max-
imum of 15 ms by spinning in a loop that yields the
current time quantum. During this loop, the thread re-
peatedly checks to see if other threads are making pro-
gress by inspecting the rate at which breakpoints fire. If
progress is not detected, the waiting thread prematurely
stops its wait.
3.3. Dealing with Benign Data Races
Research on data-race detection has amply noted the
fact that not all data races are erroneous. A practical
data-race detection tool should effectively prune or
deprioritize these benign data races when reporting to
the user. However, inferring whether or not a data race
is benign can be tricky and might require deep under-
standing of the program. For instance, a data race be-
tween two concurrent non-atomic counter updates
might be benign if the counter is a statistic variable
whose fidelity is not important to the behavior of the
program. However, if the counter is used to maintain
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
the number of references to a shared object, then the
data race could lead to a memory leak or a premature
free of the object.
During the initial runs of the tool, we found that around
90% of the data-race reports are benign. Inspecting the-
se we identified the following patterns that can be iden-
tified through simple static and/or dynamic analysis and
incorporated them in a post-process pruning phase.
Statistics Counters: Around half of the benign data
races involved conflicting updates to counters that
maintain various statistics about the program behavior
[21]. These counters are not necessarily write-only and
could affect the control flow of the program. A com-
mon scenario is to use these counter value to perform
periodic computation such as flushing a log buffer. If
DataCollider reports several data races involving an
increment instruction and the value of the memory loca-
tion consistently increases across these reports, then the
pruning phase tags these data races as statistics-counter
races. Checking for an increase in memory values helps
the pruning phase in distinguishing these statistics
counters from reference counters that are usually both
incremented and decremented.
Safe Flag Updates: The next prominent class of benign
races involves a thread reading a flag bit in a memory
location while another thread updates a different bit in
the same memory location. By analyzing few memory
instructions before and after the memory access, the
pruning phase identifies read-write conflicts that in-
volve different bits. On the other hand, write-write con-
flicts can result in lost updates (as shown in Figure 1)
and are not tagged as benign.
Special Variables: Some of the data races reported by
DataCollider involve special variables in the kernel
where races are expected. For instance, Windows main-
tains the current time in a variable, which is read by
many threads while being updated by the timer inter-
rupt. The pruning phase has a database of such varia-
bles and prunes races involving these variables.
While it is possible to design other patterns that identify
benign data races, one has to tradeoff the benefit of the
pruning achieved with the risk of missing real data rac-
es. For instance, we initially designed a pattern to clas-
sify two writes that write the same value as benign.
However, very few data-race reports matched this prop-
erty. On the other hand, Figure 4 shows an example of a
harmful data-race that we found involving two such
writes.
Also, we have made an explicit decision to make the
benign data races available to the user, but deprioritized
USENIX Association
USENIX Association
Data Races Reported Count
Fixed
Confirmed and Being Fixed
Under Investigation
Harmless
Total 38
Figure 3: Bugs reported to the developers after
excluding benign data-race reports.
against races that are less likely to be benign. Some of
our users are interested in browsing through the pruned
benign races to identify potential portability problems
and memory-model issues in their code. We also found
an instance where a benign race, despite being harm-
less, indicated unintended sharing in the code and re-
sulted in a design change.
4. Evaluation
There are two metrics for measuring the success of a
data-race detection tool. First, is it able to find data rac-
es that programmers deem important enough to fix?
Second, is it able to scale to a large system, which in
our case is the Windows operating system, with reason-
able runtime overheads? This section presents a case for
an affirmative claim on these two metrics.
4.1. Experimental Setup
For the discussion in this section, we applied DataCol-
lider on several modules in the Windows operating sys-
tem. DataCollider has been has been used on class driv-
ers, various PnP drivers, local and remote file system
drivers, storage drivers, and the core kernel executive
itself. We are successfully able to boot the operating
system with DataCollider and run existing kernel stress
tests.
4.2. Bugs Found
Figure 3 presents the data race reports produced by the
different versions of DataCollider during its entire de-
velopment. We reported a total 38 data-race reports to
the developers. This figure does not reflect the number
of benign data races pruned heuristically and manually.
We defer the discussion of benign data races to Section
4.4.
Of these 38 reports, 25 have been confirmed as bugs
and 12 of which have already been fixed. The develop-
ers indicated that 5 of these are indeed harmless. For
instance, one of the benign data races results in a driver
issuing an idempotent request to the device. While this
could result in a performance loss, the expected fre-
quency of the data race did not justify the cost of add-
ing synchronization in the common case. Identifying
such benign races requires intimate knowledge of the
code and would not be possible without the program-
mers help.
As DataCollider naturally delays the racing access that
temporally occurs first, it is likely to explore both out-
comes of the race. Despite this, only one of the 38 data
races crashed the kernel in our experiments. This indi-
cates that the effects of an erroneous data race are not
immediately apparent for the particular input or the
hardware configuration of the current run.
We discuss two interesting error reports below
4.2.1. A Boot Hang Caused by a Data Race
A hardware vendor was consistently seeing a kernel
hang at boot-up time. This was not reproducible in any
of the in-house machine configurations, till the vendor
actually shipped the hardware to the developers. After
inspecting the hang, a developer noticed a memory cor-
ruption in a driver that could be a result of a race condi-
tion. When analyzing the driver in question, DataCol-
lider found the data race in an hour of testing on a regu-
lar in-house machine (in which the kernel did not hang).
Once the source of the corruption was found (perform-
ing a status update non-atomically), the bug was imme-
diately fixed.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) —-159
void AddToCache() {
//
A: x &= ~(FLAG NOT DELETED) ;
B: x |= FLAG CACHED;
MemoryBarrier();
//
}
AddToCache();
assert( x & FLAG CACHED );
Figure 4: An erroneous data race when the
AddToCache function is called concurrently.
Though the data race appears benign, as the con-
flicting accesses “write the same values,” the as-
sert can fail on some thread schedules.
4.2.2. A Not-So-Benign Data Race
Figure 4 shows an erroneous data race. The function
AddToCache performs two non-atomic updates to the
flag variable. DataCollider produced an error report
with two threads simultaneously updating the flag at
location B. Usually, two instructions writing the same
values is a good hint that the data race is benign. How-
ever, the presence of the memory barrier indicated that
this report required further attention — the developer
was well aware of consequences of concurrency and the
rest of the code relied on crucial invariants on the flag
updates. When we reported this data race to the devel-
oper he initially tagged it as benign. On further discus-
sion, we discovered that the code relied on the invariant
that the CACHED bit is set after a call to AddToCache.
The data race can break this invariant when a concur-
rent thread overwrites CACHED bit when performing the
update at A, but gets preempted before setting the bit at
B.
4.2.3. How Fixed
While data races can be hard to find and result in mys-
terious crashes, our experience is that most are relative-
ly easy to fix. Of the 12 bugs, 3 were the result of miss-
ing locks. The developer could easily identify the lock-
ing discipline that was meant to be followed, and could
decide which lock to add without the fear of a deadlock.
6 data races were the fixed by using an atomic instruc-
tions, such as interlocked increment, to make a read-
modify-write to a shared variable. 2 bugs were a result
of unintended sharing and were fixed by making the
particular variable thread local. Finally, one bug indi-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
0.30x
0.25x
3
© 0.20x
£ -*
§ 0.15x =
o ° ges
@ 0.10x t.
ion e ? * e
— 0.05 ~—* a
zie ee
0.00x t S$ > 7 1
500 1000
-0.05x ?
Average Code Breakpoints Hit / Second
Figure 5: Runtime overhead of DataCollider with in-
creasing sampling rate, measured in terms of the num-
ber of code breakpoints firing per second. The over-
head tends to zero as the sampling rate is reduced, in-
dicating that the tool has negligible base overhead.
80 -
60
wn a
g
& 40 +
£ °
o
a -¢
20 +,
. e a 7 P oe % . 2
0 -t-ang@e"s : , : :
0.00x 0.10x 0.20x 0.30x
Runtime Overhead
Figure 6: The number of data races, uniquely identi-
fied by the pair of racing program locations, with the
runtime overhead. DataCollider is able to report data
race even under overheads under 5%
cated a broken design due to a recent refactoring and
resulted in a design change.
4.3. Runtime Overhead
Users have an inherent aversion to dynamic analysis
tools that add prohibitive runtime overheads. The obvi-
ous reason is the associated wastage of test resources —
a slowdown of ten means that only one-tenth the
amount of testing can be done with a given amount of
resources. More importantly, runtime overheads intro-
duced by a tool can affect the real-time execution of the
USENIX Association
USENIX Association
Data Race Category Count
: Statistic Counter
Benign - Safe Flag Update
Heuristically : :
Paanad Special Variable
Subtotal 86
: Double-check locking
oe Volatile
Bauned . Write Same Value
Other
Subtotal 18
Real Confirmed
Investigating
Subtotal 9
Total 113
Figure 7: Categorization of data races found by
DataCollider during kernel stress.
program. The operating system could start a recovery
action if a device interrupt takes too long to finish. Or a
test harness can incorrectly tag a kernel-build faulty if it
takes too long to boot.
To measure the runtime overhead of DataCollider, we
repeatedly measured the time taken for the boot-
shutdown sequence for different sampling rates and
compared against a baseline Windows kernel running
without DataCollider. These experiments where done
on the x86 version of Windows 7 running on a virtual
machine with 2 processors and 512 MB memory. The
host machine is an Intel Core2-Quad 2.4 GHz machine
with 4 GB memory running Windows Server 2008.
The guest machine was limited to 50% of the pro-
cessing resources of the host. This was done to prevent
any background activity on the host from perturbing the
performance of the guest.
Figure 5 shows the runtime overhead of DataCollider
for different sampling rates, measured by the average
number of code breakpoints fired per second during the
run. As expected, the overhead increases roughly line-
arly with the sampling rate. More interestingly, as the
sampling rate tends to zero, DataCollider’s overhead
reaches zero. This indicates that DataCollider can be
“always on” in various testing and deployment scenari-
os, allowing the user to tune the overhead to any ac-
ceptable limit.
Figure 6 shows the number of data races detected for
different runtime costs. DataCollider is able to detect
data races even for overheads less than 5% indicating
the utility of the tool at low overheads.
4.4. Benign Data Races
Finally, we performed an experiment to measure the
efficacy of our pruning algorithm for benign data races.
The results are shown in Figure 7. We enabled
DataCollider while running kernel stress tests for 2
hours sampling at approximately 1000 code breakpoints
per second. DataCollider found a total of 113 unique
data races. The patterns described in Section 3.3 can
identify 86 (76%) of these as benign errors. We manu-
ally (and painfully) triaged these reports to ensure that
these races were truly benign. Of the remaining races,
we manually identified 18 as not erroneous. 8 of them
involved the double-checked locking idiom, where a
thread performs a racy read of a flag without holding a
lock, but reconfirms the value after acquiring the lock.
8 were accesses to volatile variables that DataCollider’s
analysis was unable to infer the type of. These reports
can be avoided with a more sophisticated analysis for
determining the program types. This table demonstrates
that a significant percentage of benign data races can be
heuristically pruned without risks of missing real data
races. During this process, we found 9 potentially harm-
ful data races of which 5 have already been confirmed
as bugs.
5. Conclusion
This paper describes DataCollider, a lightweight and
effective data-race detector specifically designed for
low-level systems code. Using our implementation of
DataCollider for the Windows operating system, we
have found to date 25 erroneous data races of which 12
are already fixed.
We would like to thank our shepherd Junfeng Yang and
all our anonymous reviewers for valuable feedback on
the paper.
References
[1] Stefan Savage, Michael Burrows, Greg Nelson,
and Patrick Sobalvarro, "Eraser: A Dynamic Data
Race Detector for Multithreaded Programs," ACM
Transactions on Computer Systems, vol. 15, no. 4,
pp. 391-411, 1997.
[2] Robert O'Callahan and Jong-Deok Choi, "Hybrid
Dynamic Data Race Detection," SIGPLAN Not.,
vol. 38, no. 10, pp. 167-178, 2003.
[3] Yuan Yu, Tom Rodeheffer, and Wei Chen,
"RaceTrack: Efficient Detection of Data Race
Conditions via Adaptive Tracking," in Symposium
on Operating System Principles (SOSP), 2005, pp.
221-234.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI’10) 161
162
[4] Michael D Bond, Katherine E Coons, and Kathryn
S McKinley, "PACER: Proportional Detection of
Data Races," in Programming Languages Design
and Implementation (PLDI), 2010.
[5] Daniel Marino, Madanlal Musuvathi, and Satish
Narayanasami, "LiteRace: Effective Sampling for
Lightweight Data-Race Detection," in
Programming Language Design and
Implementation, 2009, pp. 134-143.
[6] Cormac Flanagan and Stephen N_ Freund,
"FastTrack: Efficient and Precise Dynamic Race
Detection," in Programming Language Design and
Implementation, 2009, pp. 121-133.
[7] E Pozniansky and A_ Schuster, "MultiRace:
Efficient on-the-fly data race detection in
multithreaded C++ programs," Concurrency and
Computation: ractice and Experience, vol. 19, no.
3, pp. 327-340, 2007.
[8] Leslie Lamport, "Time, clocks, and the ordering of
events in a distributed system," Communications
of the ACM, vol. 21, no. 7, pp. 558-565, 1978.
[9] Paul Sack, Brian E Bliss, Zhiqiang Ma, Paul
Petersen, and Josep Torrellas, "Accurate and
Efficient Filtering for the Intel Thread Checker
Race Detector," in Workshop on Architectural and
System Support for Improving Software
Dependability, 2006, pp. 34-41.
[10] Hans Boehm and Sarita Adve, "Foundations of the
C++ Concurrency Memory Model," HP Labs,
Technical Report HPL-2008-56 , 2008.
[11] Hans Boehm. (2009, Sep.) N1411: Memory Model
Rationale. [Online]. http://www.open-
std.org/JTC1/SC22/WG14/www/docs/n1411.htm
[12] IEEE, POSIX.1c, Threads extensions, 1995, IEEE
Std 1003.1c.
[13] Dawson Engler and Ken Ashcraft, "RacerX:
Effective, Static Detection of Race Conditions and
Deadlocks," in Symposium on Operating Systems
Principles (SOSP), 2003, pp. 237-252.
[14] Mayur Naik, Alex Aiken, and John Whaley,
"Effective Static Race Detection for Java," in
Programming Language Design and
Implementation (PLDI), 2006, pp. 308-319.
[15] Cormac Flanagan and Stephen Freund, "Type-
Based Race Detection for Java," in Programming
Language Design and Implementation (PLDI),
Vancouver, 2000, pp. 219-232.
[16] Zachary Anderson, David Gay, and Mayur Naik,
"Lightweight Annotations for Controlling Sharing
in Concurrent Data Structures," in Programming
Language Design and Implementation (PLDI),
Dublin, 2009.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
[17] Chandrasekhar Boyapati, Robert Lee, and Martin
Rinard, "Ownership Types for Safe Programming:
Preventing Data Races and Deadlocks," in Object-
Oriented Programming, Systems, Languages and
Applications (OOPSLA), 2002, pp. 211-230.
[18] A Dinning and E Schonberg, "Detecting access
anomalies in programs with critical sections," in
Workshop on Parallel and Distributed Debugging,
1991, pp. 85-96.
[19] The IDA Pro Disassembler and Debugger.
[Online]. http://www.hex-rays.com/idapro/
[20] Koushik Sen, "Race Directed Random Testing of
Concurrent Programs," in Programming Language
Design and Implementation (PLDI'08), 2008, pp.
11-21.
[21] Satish Narayanasamy, Zhenghao Wang, Jordan
Tigani, Andrew Edwards, and Brad Calder,
"Automatically Classifying Benign and Harmful
Data Races Using Replay Analysis," in
Programming Language Design and
Implementation (PLDI '07), 2007, pp. 22-31.
[22] Donald E. Knuth, The Art
Programming, Volume 2.:
Longman, 1997.
[23] Steven C. Woo, Moriyoshi Ohara, Evan Torrie,
Jaswinder P. Singh, and Anoop Gupta, "The
SPLASH-2 Programs: Characterization and
Methodological Considerations," in [SCA '95:
International Symposium on Computer
architecture, 1995, pp. 24-26.
[24] Amitabh Srivastava and Alan Eustace, "ATOM: A
System for Building Customized Program
Analysis Tools," in Proceedings of the ACM
SIGPLAN 1994 Conference on Programming
Language Design and Implementation, 1994, pp.
196-205.
of Computer
Addison-Wesley
USENIX Association
USENIX Association
Ad Hoc Synchronization Considered Harmful
Weiwei Xiong}, Soyeon Park, Jiagi Zhang, Yuanyuan Zhou, Zhigiang Ma*
University of California, San Diego
Abstract
Many synchronizations in existing multi-threaded pro-
grams are implemented in an ad hoc way. The first part
of this paper does a comprehensive characteristic study
of ad hoc synchronizations in concurrent programs. By
studying 229 ad hoc synchronizations in 12 programs of
various types (server, desktop and scientific), including
Apache, MySQL, Mozilla, etc., we find several interest-
ing and perhaps alarming characteristics: (1) Every stud-
ied application uses ad hoc synchronizations. Specifically,
there are 6—83 ad hoc synchronizations in each program.
(2) Ad hoc synchronizations are error-prone. Significant
percentages (22-67%) of these ad hoc synchronizations
introduced bugs or severe performance issues. (3) Ad hoc
synchronization implementations are diverse and many of
them cannot be easily recognized as synchronizations, i.e.
have poor readability and maintainability.
The second part of our work builds a tool called
SyncFinder to automatically identify and annotate ad hoc
synchronizations in concurrent programs written in C/C++
to assist programmers in porting their code to better struc-
tured implementations, while also enabling other tools
to recognize them as synchronizations. Our evaluation
using 25 concurrent programs shows that, on average,
SyncFinder can automatically identify 96% of ad hoc syn-
chronizations with 6% false positives.
We also build two use cases to leverage SyncFinder’s
auto-annotation. The first one uses annotation to detect 5
deadlocks (including 2 new ones) and 16 potential issues
missed by previous analysis tools in Apache, MySQL and
Mozilla. The second use case reduces Valgrind data race
checker’s false positive rates by 43-86%.
1 Introduction
Synchronization plays an important role in concurrent pro-
grams. Recently, partially due to realization of multi-
core processors, much work has been conducted on syn-
chronization in concurrent programs. For example, vari-
ous hardware/software designs and implementations have
been proposed for transactional memory (TM) [37, 13, 30,
40] as ways to replace the cumbersome “lock” operations.
Similar to TM, some new language constructs [46, 7, 12]
such as Atomizer [12] have also been proposed to address
{University of Illinois at Urbana-Champaign
*Intel
the atomicity problem. On a different but related note,
various tools such as AVIO [27], CHESS [31], CTrig-
ger [36], ConTest [6] have been built to detect or ex-
pose atomicity violations and data races in concurrent pro-
grams. In addition to atomicity synchronization, condition
variables and monitor mechanisms have also been studied
and used to ensure certain execution order among multiple
threads [14, 16, 22].
So far, most of the existing work has targeted only the
synchronizations implemented in a modularized way, i.e.,
directly calling some primitives such as “lock/unlock” and
“cond_wait/cond_signal” from standard POSIX thread li-
braries or using customized interfaces implemented by
programmers themselves. Such synchronization methods
are easy to recognize by programmers, or bug detection
and performance profiling tools.
Unfortunately, besides modularized synchronizations,
programmers also use their own ad hoc ways to do syn-
chronizations. It is usually hard to tell ad hoc synchro-
niztions apart from ordinary thread-local computations,
making it difficult to recognize by other programmers for
maintenance, or tools for bug detection and performance
profiling. We refer to such synchronization as ad hoc syn-
chronization. If a program defines its own synchronization
primitives as functional calls and then uses these functions
throughout the program for synchronization, then we do
not consider these primitives as ad hoc, since they are well
modularized.
Ad hoc synchronization is often used to ensure an in-
tended execution order of certain operations. Specifi-
cally, instead of calling“cond_wait()” and “cond-signal()”
or other synchronization primitives, programmers often
use ad hoc loops to synchronize with some shared vari-
ables, referred to as sync variables. According to pro-
grammers’ comments, they are implemented this way due
to either flexibility or performance reasons.
Figure 1(a)(b)(c)(d) show four real world examples
of ad hoc synchronizations from MySQL, Mozilla, and
OpenLDAP. In each example, a thread is waiting for some
other threads by repetitively checking on one or more
shared variables, i.e. sync variables. Each case has its own
specific implementation, and it is also not obviously appar-
ent that a thread is synchronizing with another thread.
Unfortunately, there have been few studies on ad hoc
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) —:163
164
/* “wait for the other guy finish
(not efficient, but rare)” */
while (crc_table_empty);
write_table(.., crc_table[0]);
for (deleted=0; ;) {
references to dbmfp */
f MYSQL? if (dbmfp->ref == 1) {
(a) direct spinning
/* wait until some waiting threads enter */
while(group->waiter->count == 0) {
deleted = 1;
/* abort if the group is not running */
if(group->state != _prmw_running) {
PR_SetError(..);
goto aborted;
} }
} /* Mozilla */
if (deleted) break;
__os_sleep(dbenv, 1, 0);
THREAD_LOCK(..., dbmp->mutex);
/* wait for other threads to release their
2
if (F_ISSET(dbmfp, MP_OPEN_CALLED))\
TAILQ_REMOVE(&dbmp->dbmfq, ..); 7
} Ss
THREAD_UNLOCK(..., dbmp->mutex); \ }
/* wait for operations on tables from other threads*/
new_activity_counter = 0;
background_loop:
tables_to_drop = drop_tables_in_background();
if(tables_to_drop > 0)
os_thread_sleep(100000);
while(n_pages_purged) {
log_butfer_flush_to_disk();
/* new activities come in, go active and serve */
if(new_activity_counter > 0)
goto loop;
else goto background_loop;
a
/* OpenLDAP */ /* MySQL 7
(b) multiple exit conditions
(c) control dependency
(d) useful work inside waiting loop
Figure |: Real world examples of ad hoc synchronizations. Sync variables are highlighted using bold fonts. Example (a) directly
spins on the sync variable; (b) checks more than one sync variables, (c) takes a certain control path to exit after checking a sync
variable, (d) performs some useful work inside the waiting loop.
synchronization. It is unclear how commonly it is used,
how programmers implement it, what issues are associated
with it, whether it is error-prone or not.
1.1 Contribution 1: Ad Hoc Synchroniza-
tion Study
In the first part of our work, we conduct a “forensic inves-
tigation” of 229 ad hoc synchronizations in 12 concurrent
programs of various types (server, desktop and scientific),
including Apache, MySQL, Mozilla, OpenLDAP, etc. The
goal of our study is to understand the characteristics and
implications of ad hoc synchronization in existing concur-
rent programs.
Our study has revealed several interesting, alarming and
quantitative characteristics as follows:
(1) Every studied concurrent program uses ad hoc syn-
chronization. More specifically, there are 6-83 ad hoc
synchronizations implemented using ad hoc loops in each
of the 12 studied programs. The fact that programmers
often use ad hoc synchronization is likely due to two pri-
mary reasons: (i) Unlike typical atomicity synchroniza-
tion, when coordinating execution order among threads,
the intended synchronization scenario may vary from one
to another, making it hard to use a common interface
to fit every need (more discussion follows below and in
Section 2); (ii) Performance concerns make some of the
heavy-weight synchronization primitives less applicable.
(2) Although almost all ad hoc synchronizations are im-
plemented using loops, the implementations are diverse,
making it hard to manually identify them among the thou-
sands of computation loops. For example, Figure 1(a) di-
rectly spins on a shared variable; Figure 1(b) has multi-
ple exit conditions; Figure 1(c) shows the exit condition
indirectly depends on the sync variable and needs com-
plicated calculation to determine whether to exit the loop;
Figure 1(d) synchronizes on program states and performs
useful work while checking whether the remote thread has
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
| Apps. || #ad hoc syne | #buggy sync
[Apache | 33___-| ‘7 22%)
OpenLDAP | 15 10 (67%)
[Cherokee [6 | 360%)
Mozillajs_[| 17 | 5 G0%)
[Transmission [| 13 [8 (62%)
Table 1: Percentages of ad hoc synchronizations that had
introduced bugs according to the bugzilla databases and
changelogs of the applications.
changed the states or not. Such characteristic may par-
tially explain why programmers use ad hoc synchroniza-
tions. More discussion and examples are in Section 2.
(3) Ad hoc synchronizations are error-prone. Table 1
shows that among the five software systems we studied,
signficant percentages (22-67%) of ad hoc synchroniza-
tions introduced bugs. Although some experts may expect
such results, our study is among the first to provide some
quantitative results to back up this observation.
Ad hoc synchronization can easily introduce deadlocks
or hangs. As shown on Figure 2, Apache had a deadlock in
one of its ad hoc synchronizations. It holds a mutex while
waiting on a sync variable “queue_info—idlers”. Figure 3
shows another deadlock example in MySQL, which has
never been reported previously. More details and the real
world examples are in Section 2.
Because they are different from deadlocks caused by
locks or other synchronization primitives, deadlocks in-
volving ad hoc synchronizations are very hard to detect
using existing tools or model checkers [11, 43, 24]. These
tools cannot recognize ad hoc synchronizations unless
these synchronizations are annotated manually by pro-
grammers or automatically by our SyncFinder described
in section 1.2. For the same reason, it is also hard for con-
currency testing tools such as ConTest [6] to expose these
deadlock bugs during testing.
Furthermore, ad hoc synchronizations also have prob-
lems interacting with modern hardware’s weak memory
USENIX Association
USENIX Association
listener thread :
apr_thread_mutex_lock(&m);
while(!ring_empty(..) as
&& expiration_time<timeout apr_atomic_inc32( queue_
&& get_worker(&idle_worker)){ h info->idlers) ;
worker thread:
apr_thread_mutex_lock(&m);
}
}
get_worker‘(..){
while(queue_info->idlers==0);—_
} /* Apache */
change log: “Never hold mutex while calling blocking operations ”
Figure 2: A deadlock introduced by an ad hoc synchro-
nization in Apache.
Hold : mutex Hold : protect_global_read
Wait : global_read_block (thread 3) Wait : mutex (thread 1)
Thread 1 Thread 2
S1.1 pthread_mutex_lock(&mutex); S2.1 protect_global_read ++;
S2.2 pthread_mutex_lock(&mutex);
S2.3 protect_global_read --;
$1.2 while(global_read_block) {...}
$1.3 pthr¢ad_mutex_unlock(&mutex);
Thread 3
$3.1 global_read_block ++;
$3.2 while(protect_global_read > 0) {...}
$3.3 global_read_block --;
Hold : global_read_block;
Wait : protect_global_read (thread 2)
/* MySQL */
Figure 3: A deadlock caused by a circular wait among
three threads (This is a new deadlock detected by our dead-
lock detector leveraging SyncFinder’s auto-annotation).
Thread 2 is waiting at $2.2 for the lock to be released by
thread 1; thread 1 is waiting at S1.2 for thread 3 to decrease
the counter at S3.3; and thread 3 is waiting at S3.2 for thread 2
to decrease another counter at $2.3.
consistency model and also with some compiler optimiza-
tions, e.g. loop invariant hoisting (discussed further in
Section 2).
By studying the comments associated with ad hoc syn-
chronizations, we found that some programmers knew
their implementations might not be safe or optimal, but
they still decided to keep their ad hoc implementations.
(4) Ad hoc synchronizations significantly impact the effec-
tiveness and accuracy of various bug detection and per-
formance tuning tools. Since most bug detection tools
cannot recognize ad hoc synchronizations, they can miss
many bugs related to those synchronizations, as well as
introduce many false positives (details and examples in
Section 2). For the same reason, performance profiling
and tuning tools may confuse ad hoc synchronizations
for computation loops, thus generating inaccurate or even
misleading results.
1.2. Contribution 2:
Synchronizations
Identifying Ad Hoc
Our characteristic study on ad hoc synchronization reveals
that ad hoc synchronization is often harmful with respect
to software correctness and performance. The first step
to address the issues raised by ad hoc synchronization is
to identify and annotate them, similar to the way that type
annotation helps Deputy [9] and SafeDrive [50] to identify
memory issues in Linux. Specifically, if ad hoc synchro-
nizations are annotated in concurrent programs, (1) static
or dynamic concurrency bug (e.g. data race and deadlock)
detectors can leverage such annotations to detect more
bugs and prune more false positives caused by ad hoc syn-
chronizations; (2) performance tools can be extended to
capture bottlenecks related to these synchronizations; (3)
new programming language/model designers can study ad
hoc synchronizations to design or revise language con-
structs; (4) programmers can port such ad hoc synchro-
nizations to more structured implementations.
Unfortunately, ad hoc synchronizations are very hard
and time-consuming to recognize and annotate manu-
ally. Partly because of this, although some annotation
languages for synchronizations like Sun Microsystems’
Lock_Lint [2] have been available for several years, they
are rarely used, even in Sun’s own code [35]. Further-
more, manual examination is also error-prone. Figure 4
shows a MySQL ad hoc synchronization example that we
missed during the manual identification we conducted for
our characteristic study. Fortunately, our automatic iden-
tification tool SyncFinder found it. We overlooked this
example because of the complicated nested “goto” loops.
loop:
if(shutdown_state > 0)
goto background_loop;
me /* MySQL “7
background_loop:
/* background operations */
if(new_activity_counter > 0)
goto loop;
else
goto background_loop;
if(shutdown_state == EXIT)
os_thread_exit(NULL)
goto loop;
Figure 4: An ad hoc synchronization missed in our manual
identification process of our characteristic study but is iden-
tified by our auto-identification tool, SyncFinder. The inter-
locked “goto” loops can easily be missed by manual identifica-
tion (Figure 1(d) shows more detailed code).
Motivated by the above reasons, the second part of our
work involved building a tool called SyncFinder to auto-
matically identify and annotate ad hoc synchronizations
in concurrent programs. SyncFinder statically analyzes
source code using inter-procedural, control and data flow
analysis, and leverages several of our observations and in-
sights gained from our study to distinguish ad hoc syn-
chronizations apart from thousands of computation loops.
We evaluate SyncFinder with 25 concurrent programs
including the 12 used in our characteristic study and 13
others. SyncFinder automatically identifies and annotates
96% of ad hoc synchronization loops with 6% false posi-
tives on average.
To demonstrate the benefits of auto-annotation of ad
hoc synchronizations by SyncFinder, we design and eval-
uate two use cases. In the first use case, we build a sim-
ple wait-inside-critical-section detector, which can iden-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) —:165
166
Total | Ad hoc
Apps. Desc. Loc. jeeps ioape
Apache 2.2.14 Web server 228K 1462 33
MySQL 5.0.86 Database server 1.0M 4265 83
OpenLDAP 2.4.21 LDAP server 272K 2044 15
Cherokee 0.99.44 Web server 60K 748 6
Mozilla-js 0.9.1 JS engine 214K 848 17
PBZip2 2-1.1.1 Parallel bzip2 3.6K 45 7
Transmission 1.83 BitTorrent client 96K 1114 13
Radiosity SPLASH-2 14K 80 12
Barnes SPLASH-2 2.3K 88 7
Water SPLASH-2 1.5K 84 9
Ocean SPLASH-2 4.0K 339 20
FFT SPLASH-2 1.0K 57 7
Table 2: The number of ad hoc synchronizations in concur-
rent programs we studied. Ad hoc sync is implemented with
an ad hoc loop using shared variables (i.e., sync variables) in it.
tify deadlock and bad programming practices involving ad
hoc synchronizations. In our evaluation, our tool detects
five deadlocks that are missed by previous deadlock detec-
tion tools in Apache, MySql and Mozilla, and, moreover,
two of the five are new bugs and have never been reported
before. In addition, even though some(16) of the detected
issues are not deadlocks, they are still bad practices and
may introduce some performance issues or future dead-
locks. The synchronization waiting loop inside a critical
section protected by locks can potentially cause cascading
wait effects among threads.
As the second use case, we extend the Valgrind [33]
data race checker to leverage the ad hoc synchronization
information annotated by SyncFinder. As a result, Val-
grind’s false positive rates for data races decrease by 43-
86%. This indicates that even though SyncFinder is not a
bug detector itself, it can help concurrency bug detectors
improve their accuracy by providing ad hoc synchroniza-
tion information.
2 Ad Hoc Synchronization Characteristics
To understand ad hoc synchronization characteristics, we
have manually studied 12 representative applications of
three types (server, desktop and scientific/graphic), as
shown on Table 2. Two inspectors separately investigated
almost every line of source code and compared the results
with each other. As shown on Table 3, in our initial study,
we missed a few ad hoc synchronizations, most of which
are those implemented using interlocked or nested goto
loops (e.g., the example in Figure 4). Fortunately, our
automatic identification tool, SyncFinder, discovers them,
and we were able to extend our manual examination to in-
clude such complicated types.
Threats to Validity. Similar to previous work, charac-
teristic studies are all subject to the validity problem. Po-
tential threats to the validity of our characteristic study are
the representativeness of applications and our examination
methodology. To address the former, we chose a variety of
concurrent programs, including four servers, three clien-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
[Apps [Payne Toops [Ta [Tp [oth]
[Apache [334 [27 2 |
P Myson [83 ps7]
Open DAP || 15] 3. [3 [2
|
Table 3: Ad hoc sync loops missed by human inspec-
tions. Two inspectors, J, and J,, investigate the same
source code separately. Most of the sync loops missed
by both inspectors (i.e., those in Apache and MySQL) are
interlocked or nested goto loops. Others (in OpenLDAP)
are for-loops doing complicated useful work and checking
synchronization condition in it, like one in Figure 1(d).
t/desktop concurrent applications as well as five scientific
applications from SPLASH-?2, all written in C/C++, one of
the popular languages for concurrent programs. These ap-
plications are well representative of server, client/desktop-
based and scientific applications, three large classes of
concurrent programs.
In terms of our examination methodology, we have ex-
amined almost every line of code including programmers’
comments. This was an immensely time consuming effort
that took three months of our time. To ensure correctness,
the process was repeated twice, each time by a different
author. Furthermore, we were also quite familiar with the
examined applications, since we have modified and used
them in many of our previously published studies.
Overall, while we cannot draw any general conclusions
that can be applied to all concurrent programs, we believe
that our study does capture the characteristics of synchro-
nizations in three large important classes of concurrent ap-
plications written in C/C++.
Finding 1: Every studied application uses ad hoc syn-
chronizations. More specifically, there are 6-83 ad hoc
synchronizations in each of the 12 studied programs.
As shown in Table 2, ad hoc synchronizations are used in
all of our evaluated programs, and some programs (e.g.
MySQL) even use as many as 83 ad hoc synchronizations.
This indicates that, in the real world, it is not rare for pro-
grammers to use ad hoc synchronizations in their concur-
rent programs.
While we are not 100% sure why programmers use ad
hoc synchronizations, after studying the code and com-
ments, we speculate there are two primary reasons. The
first is because there are diverse synchronization needs to
ensure execution order among threads. Unlike atomicity
synchronization that shares a common goal, the exact syn-
chronization scenario for order ensurance may vary from
one to another, making it hard to design a common inter-
face to fit every need (more discussion in Finding 2).
The second reason is due to performance concerns on
synchronization primitives, especially those heavyweight
ones implemented as system calls. If the synchronization
condition can be satisfied quickly, there is no need to pay
the high overhead of context switches and system calls.
USENIX Association
Total |) Total Single exit condition
Apps. sc sc | sc sc
loops || Ad hoc | -dir | -df | -cf | -func
Apache 1462 33 4 0 1 3
MySQL | 4265 83 23 5 4 11
OpenLDAP | 2044 15 2 0 0 2
Cherokee | 748 6 0 2 0 1
Mozilla-js | 848 17 2 4 1 4
PBZip2 45 7 0 0 0 1
Transmission] 1114 13 6 0 0 1
Radiosity 80 12 5 5 1 0
Barnes 88 7 6 1 0 0
Water 84 9 9 0 0 0
Ocean 339 20 20 0 0 0
FFT 57 7 at 0 0 0
Multiple exit cond. | Total
mec | mc async | sc : single exit cond.
total -dir: directly depends
total -all |-Nall ve func a a oe ty
8 22 3 25 16 25 -df: has data
2B | 13 |27| 40 32 | 64 JGependeney
-cf : has control
4 4 7 11 9 15 dependency
3 0 3 3 1 5
mc : multiple exit cond.
10 4 1 3 5 15 -all: all exit conditions
1 0 6 6 7 7 depend on sync vars
-Nall : not all, but at least
7 0 6 6 3 2 one does
11 1 0 1 0 1 ;
7 0 0 0 0 0 func: inter-procedural
dependency
9 0 0 0 0 0 async: useful work while
20 0 0 0 0 0 waiting
7 0 0 0 0 0
Table 4: Diverse ad hoc synchronizations in concurrent programs we studied. (i) The number of exit conditions in synchroniza-
tion loops are various (sc vs. mc); (ii) There can be multiple, different types of dependency relations between sync variables and loop
exit conditions (-dir, -df, -cf, -func); (iii) Some synchronization loops do useful work with asynchronous condition checking (async).
while(1) {
int oldcount = (global->barrier).count;
If(updatedcount == oldcount) break;
} /* SPLASH2 */ a
!queue
(a) sc-df (data dependency) queue
int finished = 0; break;
for(i= 0;i< 1000 && I finished; | ++) }
if(global->pbar_count >= n_proc)
finished = 1;
} /* Radiosity*/
/* wait for the next block from
a producer queue */
safe_mutex_
Bool queue::getData(ElemPtr &fileData) {
ElemPtr &headElem = qData[head];
lock(fifo->m ut); bee
/* search qData to find the requested
block. If finds out, return true;
otherwise, return false */
/* PBZip2 */
->empty &&
->getData(Data) )
(b) mc-Nall (Some are local exit conditions)
(c) Function call
Figure 5: Examples of various ad hoc synchronizations. A sync variable is highlighted using a bold font. An arrow shows the
dependency relation from a sync variable to a loop-exit condition. The examples of other ad hoc categories are shown on Figure 1.
Such performance justifications are frequently mentioned
in programmers’ comments associated with ad hoc syn-
chronization implementations.
While ad hoc synchronizations are seemly justified, are
they really worthwhile? What are their impact on pro-
gram correctness and interaction with other tools? Can
they be expressed using some common, easy-to-recognize
synchronization primitives? We will dive into these ques-
tions in our finding 3 and 4, trying to shed some lights into
the tradeoffs.
Finding 2: Ad hoc synchronization is diverse.
Table 4 further categorizes ad hoc synchronizations from
several perspectives. Some real world examples for each
category can be found in Figure | and Figure 5.
(i) Single vs. multiple exit conditions: Some ad hoc syn-
chronization loops have only one exit condition '. We
call such sync loops sc loops. Unfortunately, many oth-
ers (up to 86% of ad hoc synchronizations in a program)
have more than one exit condition. We refer to them as
mc loops. In some of them (referred to as mc-_all), all exit
"A condition that can break the execution out of a loop.
USENIX Association
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
conditions are satisfied by remote threads. In the other
loops (referred to as mc_Nall), there are also some local
exit conditions such as time-outs, etc., that are indepen-
dent of remote threads and can be satisfied locally.
(ii) Dependency on sync variables: The simplest ad hoc
synchronization is just directly spinning on a sync variable
as shown on Figure 1(a). In many other cases (50-100%
of ad hoc synchronizations in a program), exit conditions
indirectly depend on sync variables via data dependencies
(referred to as df, Figure 5(a)), control dependencies (re-
ferred to as cf, (Figure 1(c)), even inter-procedural depen-
dencies (referred to as func, Figure 5(c)).
(iii) Asynchronous synchronizations (referred as async):
In some cases (77% of ad hoc synchronizations in
server/desktop applications we studied), a thread does
not just wait in synchronization. Instead, it also per-
forms some useful computations while repetitively check-
ing sync variables at every iteration. For example, in Fig-
ure 1(d), a MySQL master thread does background tasks
like log flushing until a new SQL query arrives (by check-
ing new_activity_counter).
167
168
for (;;) {
if (m_skip_auto_increment &&
readAutolncrementValue(...)
Il getAutoIncrementValue(...){
if (--retries && ...) {
my_sleep(retry_sleep);
continue; —_/* 30 ms sleep
for transaction */
/* get tuple Id of a table */
do {
ret= m_skip_auto_increment ?
readAutolncrementValue(...):
getAutolncrementValue(:::);
} while(ret== -1 && --retries && ..);
}
} break;
/* MySQL */||}
Figure 6: An ad hoc synchronization in MySQL was revised
by programmers to solve a performance problem.
Finding 3: Ad hoc synchronizations can easily intro-
duce bugs or performance issues.
After studying the 5 applications listed in Table 1, we
found that 22-67% of synchronization loops previously
introduced bugs or performance issues. These high issue
rates are alarming, and, as a whole, may be a strong sign
that programmers should stay away from ad hoc synchro-
nizations.
For each ad hoc synchronization loop, we use its corre-
sponding file and function names to find out in the source
code repository if there was any patch associated with it.
If there is, we manually check if the patch involves the ad
hoc sync loop. We then uses this patch’s information to
search the bugzilla databases and commit logs to find all
relevant information. By examining such information as
well as the patch code, we identify whether the patch is a
feature addition, a bug not related to synchronization, or
a bug caused exactly by the ad hoc sync loop. We only
count the last case.
Besides deadlocks (as demonstrated in Figure 2 and 3),
ad hoc synchronization can also introduce other types of
concurrency bugs. In some cases, an ad hoc synchroniza-
tion fails to guarantee an expected order and lead to a crash
because the exit condition can be satisfied by a third thread
unexpectedly. Due to space limitations, we do not show
those examples here.
In addition to bugs, ad hoc synchronizations can also
introduce performance issues. Figure 6 shows such an ex-
ample. In this case, the busy wait can waste CPU cycles
and decrease throughput. Therefore, programmers revised
the synchronization by adding a sleep inside the loop.
Ad hoc synchronizations also have problematic interac-
tions with modern hardware’s relaxed consistency mod-
els [5, 28, 45]. These modern microprocessors can reorder
two writes to different locations, making ad hoc synchro-
nizations such as the one in Figure 1(a) fail to guarantee
the intended order in some cases. As such, experts rec-
ommended programmers to stay away from such ad hoc
synchronization implementations, or at least implement
synchronizations using atomic instructions instead of just
simple reads or writes [5, 28, 45].
To make things even worse, ad hoc synchronizations
also have problematic interactions with compiler opti-
mizations such as loop invariant hoisting. Programmers
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
Comment examples
Programmers are aware of better design but still
use ad hoc implementation (8%)
/* This can be built in smarter way, like pthread_cond,
but we do it since the status can come from.. */
/* By doing.. applications will get better performance and
avoid the problem entirely. Regardless, we do this...
because we’d rather write error message in this routine, ..*/
Programmers try to prevent bugs at the first place (22%)
/* We could end up spinning indefinitely with a situation
where.. The ‘i++’ stops the infinite loop *//* We can safely
wait here in the case.. without fear of deadlock because we
made.. */ /* This spinning actually isn’t necessary except
when the compiler does corrupt 64bit arithmetic.. */
Programmers explicitly state their sync assumptions (75%)
/* GC doesn’t set the flag until it has waited for all active
requests to end */ /* We must break the wait if one of the
following occurs: i).. ii).. iii).. iv).. v).. */
Table 5: Observations in programmers’ comments on ad
hoc synchronization from Apache, Mozilla, and MySQL. We
study 63 comments associated with ad hoc synchronizations.
should avoid such optimizations on sync variables, and
ensure that waiting loops always read the up-to-date val-
ues instead of the cached values from registers. As a
workaround, programmers may need to use wrapping vari-
able accesses with function calls [3]. All of these just
complicate programming as well as software testing and
debugging.
Interestingly, some programmers are aware of the above
ad hoc synchronization problems but still use them. We
study the 63 comments associated with ad hoc synchro-
nizations in MySQL, Apache, and Mozilla. As illustrated
in Table 5, programmers sometimes mentioned better al-
ternatives, but they still chose to use their ad hoc imple-
mentations for flexibility. In some cases, they explicitly
indicated their preference for the lightness and simplicity
of ad hoc spinning loops, especially when the synchro-
nizations were expected to rarely occur or rarely need to
wait long. Also, programmers often explicitly stated their
assumptions/expectation in comments about what remote
threads should do correspondingly, since ad hoc synchro-
nizations are complex and hard to understand.
Finding 4: Ad hoc synchronizations can significantly
impact the effectiveness and accuracy of concurrency
bug detection and performance profiling tools.
As mentioned earlier, since existing concurrency bug
(deadlock, data race) detection tools cannot recognize ad
hoc synchronizations, they will fail to detect bugs that
involve such synchronizations (e.g. deadlock examples
shown on Figure 2 and 3).
In addition, they can also introduce many false posi-
tives. It has been well known that most data race detectors
incur high false positives due to ad hoc synchronizations.
Such false positives come from two sources: (1) Benign
USENIX Association
USENIX Association
Thread 1
#define LAST_PHASE 1
loop:
if(state < LAST_PHASE)
goto loop;
Thread 2
state = EXIT_THREADS;
/* MySQL */
#define EXIT_THREADS 3)|S1_ q_info->pools = new_recycle;
$2 atomic_inc( &(q_info->idlers) );
Worker Listener
S3_ while(q_info->idlers == 0) {...}
Fe, S4 first_pool = q_info->pools;
a /* Apache */
(a) Benign data race on state
(b) False data race on q_info->pools
Figure 7: False positives in Valgrind data race detection due to ad hoc synchronizations.
data races on sync variables: typically an ad hoc synchro-
nization is implemented via an intended data race on sync
variables. Figure 7(a) shows such a benign data race re-
ported by Valgrind [33] in MySQL. (2) False data races
that would never execute in parallel due to the execution
order guaranteed by ad hoc synchronizations: For exam-
ple, in Figure 7(b), the two threads are synchronized at S2
and $3, which guarantees the correct order between S1 and
S4’s accesses to g_info—pools. S1 and S4 would never
race with each other. However, most data race checkers
cannot recognize this ad hoc synchronization and, as a re-
sult, incorrectly report S1 and S4 as a data race.
Synchronization is also a big performance and scala-
bility concern because time waiting at synchronization is
wasted. Unfortunately, existing work in synchronization
cost analysis [25, 32] and performance profiling [29] can-
not recognize ad hoc synchronizations, and therefore the
synchronizations can easily be mistaken as computation.
As a result, the final performance profiling results may
cause programmers to make less optimal or even incorrect
decisions while performance tuning.
Replacing with synchronization primitives. Our find-
ings above reveal that ad hoc synchronization is often
harmful in several respects. Therefore, it is desirable
that programmers use synchronization primitives such
as cond_wait, rather than ad hoc synchronization. Fig-
ure 8 shows how ad hoc synchronization can be replaced
with a well-known synchronization primitive, POSIX
pthread_cond_wait(). Note that it may not always be
straightforward to use existing synchronization primitives
to replace all ad hoc synchronizations, because existing
synchronization primitives may not be sufficient to meet
/* “wait for the other guy to finish while(1) {
(not efficient, but rare)” */ int oldcount = (global->barrier).count;
while (crc_table_empty); i
write_table(out, crc_table[0]); if(updatedcount == oldcount) break;
f ry
ai Jt
pthread_mutex_lock(&mutex); pthread_mutex_lock(&mutex);
while (crc_table_empty) { while(1) {
pthread_cond_wait(&cond_var, int oldcount = (golbal->barrier).count;
&mutex); ona,
if(updatecount == oldcount) break;
pthread_mutex_unlock(&mutex); pthread_cond_wait(&cond_var, &mutex);
pthread_mutex_unlock(&mutex);
(b) SPLASH2
write_table(.., crc_table[0]);
(a) MySQL
Figure 8: Replacing ad hoc synchronizations with synchro-
nization primitives using condition variables. (a) shows the
re-implementation of ad hoc synchronization in Figure 1(a); (b)
is for Figure 5(a).
the diverse synchronization needs as well as the perfor-
mance requirements, as discussed in Finding 1.
3 Ad hoc Synchronization Identification
3.1 Overview
As ad hoc synchronizations have raised many challenges
and issues related to correctness and performance, it would
be useful to identify and annotate them. Manually doing
this is tedious and error-prone since they are diverse and
hard to tell apart from computation. Therefore, the second
part of our work builds a tool called SyncFinder to auto-
matically identify and annotate them in the source code of
concurrent programs. The annotation can be leveraged in
several ways as discussed in Section 1.2.
There are two possible approaches to achieve the above
goal. One is dynamic and is done by analyzing run-time
traces. The other approach is static, involving the analysis
of source code. Even though the dynamic approach has
more accurate information than the static method, it can
incur large (up to 30X [27]) run-time overhead to collect
memory access traces. In addition, the number of ad hoc
synchronizations that can be identified using this method
would largely depend on the code coverage of test cases.
Also some ad hoc synchronization loops may terminate
after only one iteration, making it hard to identify them as
ad hoc synchronization loops [18]. Due to these reasons,
we choose the static method, i.e., analyzing source code.
The biggest challenge to automatically identify ad hoc
synchronizations is how to separate them from computa-
tion loops. The diversity of ad hoc synchronizations makes
it especially hard. To address the above challenge, we have
to identify the common elements among various ad hoc
synchronization implementations.
Commonality among ad hoc synchronizations: Interest-
ingly, ad hoc synchronizations are all implemented using
loops, referred to as sync loops (Figure 9). While a sync
loop can have many exit conditions, at least one of them
is the exit condition to be satisfied when an expected syn-
chronization event happens. We refer to such exit condi-
tions as sync conditions. The sync condition directly or
indirectly depends on a certain shared variable (referred
as a sync variable) that is loop-invariant locally, and mod-
ified by a remote thread.
Note that a sync variable may not necessarily be directly
used by a sync condition (e.g., inside a while loop condi-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) —- 169
170
—_
on-shared variable;
Synchronization
pair identification
i dependency tracking
Loop Exit condition , Exit dependent variable
detection extraction (EDV) identification
a eR _an exit condition
ly Aonicte update ae Annotation,
sync. pair Serial pat pruning Repo
candidates
—» | Loop-variant based, )__»
\ and condvya loop
pruning
leaf-EDVs —=—~_ sync loop
Identified sync pairs,
sync variables,
loop structures
Figure 10: SyncFinder design to automatically identify and annotate ad hoc synchronization
flag : Synchronization variable, W flag :Synchronization write
T1 T2 T1 T2
Sync. Sync. if R flag
loop —aFiflag @Wilag loop / s} *._.
CGF \ W flag
compu-
tation
(a) spinning (b) asynchronous checking
Figure 9: Ad hoc synchronization abstract model. The loop
exit condition (i.e., sync condition) either directly or indirectly
depends on a sync variable.
tion). Instead, a syne condition may have data/control-
dependency on it like in the examples shown on Fig-
ure 1(c) and Figure 5(a)(c).
Following the above characteristic, SyncFinder starts
from loops in the target programs, and examines their exit
conditions to identify those that are (1) loop invariant, (2)
directly or indirectly depend on a shared variable, and (3)
can be satisfied by a remote thread’s update to this vari-
able. By checking these constraints, SyncFinder filters out
most computation loops as shown in our evaluation.
Checking all of the above conditions requires
SyncFinder to conduct (1) program analysis to know
the exit conditions for each loop; (2) data and control flow
analysis to know the dependencies of exit conditions;
(3) some static thread analysis to conservatively identify
what segment of code may run concurrently; and (4) some
simple satisfiability analysis to check whether the remote
update to the sync variable can satisfy the sync condition.
As shown on Figure 10, SyncFinder consists of the fol-
lowing steps: (1) Loop detection and exit condition ex-
traction; (2) Exit dependent variable (EDV) identification;
(3) Pruning computation and condvar loops based on char-
acteristics of EDVs; (4) Synchronization pairing to pair an
identified sync loop with a remote update that would break
the program out of this sync loop; (5) Final result reporting
and annotation in the target program’s source code.
SyncFinder is built on top of the LLVM compiler in-
frastructure [23] since it provides several useful basic fea-
tures that SyncFinder needs. LLVM’s intermediate repre-
sentation (IR) is based on single static assignment (SSA)
form, which automatically provides a compact definition-
use graph and control flow graph for every function,
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
both of which can be leveraged by SyncFinder’s data-,
and control-flow analysis. In addition, SyncFinder also
uses LLVM’s loopinfo analysis, alias analysis, and con-
stant propagation tracking to implement the ad hoc sync
loop identification algorithm. SyncFinder annotation is
done via the static instrumentation interfaces provided in
LLVM. In the rest of this section, we focus on our algo-
rithms and do not go into details about the basic analysis
provided by LLVM.
3.2 Finding Loops
[Apps] while [for | gow [Toul]
[Apache _[[- 27 [4 _[ 2
3
[Myson] 33_ [4 26 [8
[OpentDAP [7 [4 | 4 | 3 _|
[ Mozillajs [124 1_| 17]
Table 6: Loop mechanisms used for real-world ad hoc syn-
chronization. There are a non-negligible number of ”goto”
loops, which often complicate loop analysis (e.g., Figure 4).
As shown in Table 6, ad hoc synchronizations are imple-
mented using three primary forms of loops: “while”, “for”
and “goto”. Fortunately, LLVM’s loopinfo pass identifies
all those loops based on back edges in LLVM IR.
For each loop identified by LLVM, SyncFinder extracts
its exit conditions. Specifically, it identifies the basic
blocks with at least one successor outside of the loop, then
for each identified basic block, SyncFinder extracts its ter-
minator instruction, from which SyncFinder can identify
the branch conditions. Such conditions are the exit con-
ditions for this loop. SyncFinder represents the exit con-
ditions in a canonical form: disjunction (OR) of multiple
conditions, and examines each separately.
In addition, since LLVM does not keep the loop context
information, e.g., loop headers and bodies, across func-
tions, SyncFinder keeps track of them into its own data
structure and uses them throughout the analysis.
3.3 Identifying Sync Loops
The key challenge of SyncFinder is to differentiate sync
loops from computation loops. To address this challenge,
SyncFinder examines the exit conditions of each loop by
going through the following steps to filter out computation
loops.
USENIX Association
USENIX Association
initial _, | data/ctrl flow
EDV set analysis a
no yes leaf
leaf RDV procedure2> —> <_leaf var? >> EDVs
summary
— >|inter-procedure Return/argu.
Bs analysis Dependent Variables
Figure 11: Leaf-EDV identification. SyncFinder recursively
tracks Exit Dependent Variables(EDVs) along the data-, control-
flow, until it reaches a leaf-EDV.
(1) Exit Dependent Variable (EDV) analysis : For each
exit condition of each loop in the target program, the first
step is to identify all variables that this exit condition de-
pends on—we refer to them as exit dependent variables
(EDVs). If a loop is a sync loop, the sync variables
should be included in its EDVs. Note that a sync vari-
able is not necessarily used in an exit condition (sync con-
dition) directly. A loop exit condition can be data/control-
dependent on a sync variable. Therefore, we conduct data-
flow and control-flow analysis to find indirect EDVs. The
EDV identification process is similar to static backward
slicing [48, 38, 15].
SyncFinder first starts from variables directly refer-
enced in the exit condition. They are added into an EDV
set. Then, as shown in Figure 11, it pops a variable out
from the EDV set, and finds out new EDVs along this vari-
able’s data/control flow. New EDVs are inserted into the
set. It then pops another EDV from the set, and so on so
forth until it reaches the loop boundary. For an EDV that
does not depend on any other variables inside this loop, we
refer them as a leaf-EDV (similar to “live-in” variables).
SyncFinder maintains a separate set for leaf-EDVs. Ob-
viously, leaf-EDVs are the ones we should focus on since
they are not derived from any other EDVs in this loop.
During the backward data/control flow tracking process,
if the dependency analysis encounters a function whose
return value or passed-by-reference arguments affect the
loop exit condition, SyncFinder further tracks the depen-
dency via inter-procedural analysis. SyncFinder applies
data- and control-flow analysis starting from the function’s
return value, and identifies Return/arguments-Dependent
Variables (RDVs) in the callee. Such RDVs are also added
into the leaf-EDV set. In addition, all RDVs of this func-
tion are stored in a summary to avoid analyzing this func-
tion again for other loops.
To handle variable and function pointer aliasing,
SyncFinder leverages and extends LLVM’s alias analysis
to allow it go beyond function boundary.
(2) Pruning computation loops For every exit condition
of a loop, SyncFinder applies the following two pruning
steps to check whether it is a sync condition. At the end,
if a loop has at least one sync condition, it is identified as
a sync loop. Otherwise, it is pruned out as a computation
loop. Most computation loops are filtered in this phase.
Non-shared variable pruning: A sync variable should
be a shared variable that can be set by a remote thread.
Specifically, it should be either a global variable, a heap
object, or a data object (even stack-based) that is passed to
a function (e.g., thread starter function) called by another
thread, which can be shared by the two threads.
Therefore, if an exit condition has no shared variables in
its leaf-EDV set, it is deleted from the loop’s exit condition
set. SyncFinder moves to the next exit condition of this
loop. If the loop has no exit conditions left, this loop is
pruned out as a computation loop.
Loop-variant based pruning: — In almost all cases, a
sync condition is loop-invariant locally, and only a remote
thread changes the result of the sync condition. Based on
this observation, SyncFinder prunes out those exit condi-
tions that are loop-variant locally as shown on Figure 12.
It is possible that some ad hoc synchronizations may also
change the sync conditions locally. In all our experiments
with 25 concurrent programs, we did not find any true ad
hoc synchronizations that SyncFinder missed due to this
pruner. Note that some exit conditions, such as expiration
time, are separated as different conditions, and we exam-
ine each condition separately.
while(module){
next = module->next;
free(module);
module = next; =} /* Mozilla */) | } /* SPLASH /
(a) Loop-variant module (b) Loop-variant condition checking
for (i = 0; i < nlights; i++){
VecMatMult(Ip->pos, m, Ip->pos);
Ip = lp->next;
Figure 12: The non-syne variables pruned out by loop-
variant based pruning. In the two computation loops, the vari-
ables in italic font are shared variable leaf-EDVs.
To check if an exit condition is loop variant, SyncFinder
applies a modification (IZOD) analysis within the scope
of a loop being examined. Specifically, it checks all leaf-
EDVs and leaf-RDVs of this loop, and prunes out those
modified locally within this loop. The leaf-RDV summary
is also updated accordingly.
(3) Pruning condvar loops: SyncFinder does not con-
sider condvar loops (i.e., sync loops that are associated
with cond_wait primitives) as ad hoc loops as they can be
easily recognized by intercepting or instrumenting these
primitives. As the final step of the ad hoc sync loop identi-
fication, SyncFinder checks every loop candidate to see it
calls a cond_wait primitive inside the loop. Loops that use
primitives are recognized as condvar loops and are thereby
pruned out. The names of cond_wait primitives(original
pthread functions or wrappers) are provided as input to
SyncFinder to identify cond_wait calls.
3.4 Synchronization Pairing
Once we identify a potential sync loop, we find the re-
mote update (referred as a sync write) that would “release”
(break) the wait loop. To identify a sync write, SyncFinder
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI’10) 171
172
[Apps [| total_[ constant [ince op
[Apache [|_42 [21 60.0%) [51.9%
MySQL || 325_| 125 G8.5%) | 110 G3.8%)
OpenLDAP [| 203 | 48 (23.6%) | 8.9%)
Mozilla-js_ [|] 83 | 41 (49.4%) 31 (37.3%)
Table 7: The characteristics of writes to sync variables. In
the four sampled applications, majority of writes assign constant
values, or use simple increase or decrease operations.
first collects all write instructions modifying sync variable
candidates, and then applies the following pruning steps.
Pruning unsatisfiable remote updates For each remote
update to the target sync variable candidate, SyncFinder
analyzes what value is assigned to this variable, and
whether it can satisfy the sync condition. A complicated
solution to achieve this functionality is to use a SAT solver.
But it is too heavyweight, especially since, according to
our observations (shown in Table 7), the majority(66%)
of sync writes either assign constant values to sync vari-
ables, or use simple counting operations like incremen-
t/decrement, rather than complicated computations. This
is because a sync variable is usually a control variable (e.g.
status, flag, etc.) and does not require sophisticated com-
putations.
Therefore, instead of using a SAT solver, we use con-
stant propagation to check if this remote update would sat-
isfy the exit condition. For an assignment with a constant,
it substitutes the variable with the constant, and propagates
it till the exit condition to see if it is satisfiable or not. For
increment based updates, SyncFinder treats it as “sync var
> 0” since it obviously does not release the loop that is
waiting for an exit condition “(sync var == 0)”.
Pruning serial pairs A sync loop and a sync write should
be able to execute concurrently. If there is a happens-
before relation between such pair, due to thread cre-
ation/join, barrier, etc, the remote write does not match
with the sync loop. Due to the limitation of static analysis,
currently SyncFinder conservatively prunes serial pairs re-
lated to only thread creation/join. Specifically, SyncFinder
follows thread creation and conservatively estimates code
that might be running concurrently.
3.5 SyncFinder Annotation
After the above pruning process, the remaining ones
are identified as sync loops, along with their corre-
sponding sync writes. All the results are stored in a
file. SyncFinder also automatically annotates in the tar-
get software’s source code using LLVM static instru-
mentation framework. It inserts //#SyncAnnotation:
Sync_Loop_Begin(&loopId), //#SyncAnnotation:
Sync_Loop-End(&loopId), respectively, at the begin-
ning and end of an identified sync loop. In addi-
tion, inside the loop, it also annotates the read to
a sync variable by inserting //#SyncAnnotation:
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
Sync_Read(&syncVar, &loopId). For the corre-
sponding sync write, it inserts //#SyncAnnotation:
Sync.Write(&syncVar, &loopId). The loopld is
used to match a remote sync write with a sync loop. Sim-
ilar annotations are also inserted into the target program’s
bytecode to be leveraged by concurrency bug detection
tools as discussed in the next section.
4 Two Use Cases of SyncFinder
SyncFinder’s auto-identification can be used by many bug
detection tools, performance profiling tools, concurrency
testing frameworks, program language designers, etc. We
built two use cases to demonstrate its benefits.
4.1 A Tool to detect bad practices
It is considered bad practice to wait inside a critical sec-
tion, as it can easily introduce deadlocks like the Apache
example shown on Figure 2 and the MySQL example on
Figure 3. Furthermore, it can result in performance is-
sues caused by cascading wait effects, and may introduce
deadlocks in the future if programmers are not careful. As
a demonstration, we built a simple detector (referred to as
wait-inside-critical-section detector) to catch these cases
leveraging SyncFinder’s auto-annotation of ad hoc syn-
chronizations. Our detection algorithm can be easily in-
tegrated into any existing deadlock detection tool as well.
To detect such pattern, our simple detector checks ev-
ery sync loop annotated by SyncFinder to see if it is per-
formed while holding some locks. If a sync loop is hold-
ing a lock, then SyncFinder checks the remote sync write
to see whether the write is performed after acquiring the
same lock or after another ad hoc sync loop, so on and so
forth, to see if it is possible to form a circle. If it is, the
detector reports it as a potential issue: either a deadlock or
at least a bad practice.
4.2 Extensions to data race detection
We also extend Valgrind [33]’s dynamic data race detec-
tor to leverage SyncFinder’s auto-identification of ad hoc
sync loops. Valgrind implements a happens-before algo-
rithm [21] using logical timestamps, which was originally
based on conventional primitives including mostly lock
primitives, and thread creation/join. It cannot recognize ad
hoc synchronizations. As a result, it can introduce many
false positives (shown in Table 12) as discussed in Sec-
tion 2 and illustrated using two examples in Figure 7.
We extend Valgrind to eliminate data race false posi-
tives by considering ad hoc synchronizations annotated by
SyncFinder. It treats the end of a sync loop in a similar
way to a cond_wait operation, and the corresponding sync
write like a signal operation. This way it keeps track of
the happens-before relationship between them. We also
extend Valgrind to not consider sync variable reads and
writes as data races.
USENIX Association
USENIX Association
Apps. Total Identified Sync Loops | Missed
loops |} Total | True FP ones
Apache 1462 17 15 2 1
MySQL 4265 48 42 6 3
. OpenLDAP | 2044 18 14 4 1
= Cherokee TA8 6 6 0 0
A|| AOLServer | 496 6 6 0 -
Nginx 705 12 11 1 -
BerkeleyDB | 1006 LS 11 4 -
BIND9 1372 5 4 1 -
Mozilla-js | 848 16 11 5 1
a. PBZip2 45 7 7 0 0
2 Transmission | 1114 14 12 2 1
a HandBrake | 551 13 3 0 -
p/zip 1594 10 9 1 -
wxDFast 154 0 -
Radiosity 80 12 12 0 0
Barnes 88 7 7 0 0
Water 84 9 0 0
7 Ocean 339 20 20 0 0
= FFT 57 7 7 0 0
=|" Cholesky | 362 || 8 8 0 5
8 RayTracer | 144 3 3 0 -
FMM 108 8 8 0 -
Volrend 77 9 9 0 -
LU 38 0 0 0 -
Radix 52 14 14 0 -
Total 290 | 264 26
(Ave.) ~ 11 (11.6) | (10.6) |} (1.0) -
Table 8: Overall results of SyncFinder: Every concurrent
program uses ad hoc sync loops except LU. Both true ad hoc
sync loops and false positives are showed here. For the 12 pro-
grams used in the characteristic study, the numbers of missed ad
hoc sync loops are also reported. They are generated by com-
paring with our manual checking results from the characteristic
study. We cannot show the numbers of missed ad hoc sync loops
for the unseen programs in the study since we did not manu-
ally examine them as we did for the 12 studied programs. To
show SyncFinder’s total exploration space, we also show the to-
tal number of loops, most of which are computation loops. Note
that the total numbers of ad hoc sync loops are different from
those numbers shown in Table 2 because some code (for other
platforms such as FreeBSD, etc) are not included during the com-
pilation.
5 Evaluation
5.1 Effectiveness and Accuracy
We evaluated SyncFinder on 25 concurrent programs,
including 12 used in our manually characteristic study
and 13 other ones. Table 8 shows the overall result of
SyncFinder on the 25 programs. On average SyncFinder
accurately identifies 96% of ad hoc sync loops in the 12
studied programs and has a 6% false positive rate overall.
SyncFinder successfully identified diverse ad hoc order
synchronizations, including those we missed during our
manual identification. For example, it successfully identi-
fies those complicated, interlocked “goto” sync loops, as
shown in Figure 4.
For the 12 studied programs, SyncFinder misses a
few(1-3 per application) sync loops in large server/desk-
top applications. Considering the total number of loops
(up to 4265) in each of these applications, such a small
miss rate does not limit SyncFinder’s applicability to real
world programs. SyncFinder fails to identify these sync
loops because of the unavailability of the source code for
these library functions and inaccurate pointer alias.
SyncFinder also returns a low number of false positives
for all 25 programs. As showed in Table 8, SyncFinder has
0-6 false positives per program (i.e. a false positive rate of
0-30%). Such numbers are quite reasonable. Program-
mers can easily examine the reported sync loops to prune
out those few false positives. Most of the false positives
are caused by inaccurate function pointer analysis. Due to
complicated function pointer alias, sometimes SyncFinder
cannot further track into callee functions to check if a tar-
get variable (leaf-EDV) is locally modified. In these cases,
SyncFinder conservatively considers the target variable as
a sync variable.
5.2 Sync Loop Identification and Pruning
Apps. Total Exit Leaf- Aft non- | Aft loop- | Aft cond-
loops cond. EDVs_| -shared pr. | var. pr. var pr.
[Apache [| 1462 | 3,120 | B82
aap ante
[ MysQr [4.265]
[Opent DAPT| 2.044 [4434 [11276 |__| _43_[ 27 _
[ Pezipe [| 2 | 278 | 79 | 10 | 16 [9
Table 9: EDV Analysis and non-sync variable pruning. After
identifying leaf-EDVs for each loop, SyncFinder applies non-
shared, loop-variant and condvar-loop based pruning schemes.
The final results are the sync variables of the ad hoc sync loops.
Some sync variables may be associated with a same sync loop.
To show the effectiveness of sync loop identification,
in Table 9, we test SyncFinder on some server/desktop
applications and show the results from each of the sync
loop identification steps. From the total loops identified,
SyncFinder extracts exit conditions, and identifies all leaf-
EDVs (the third column in Table 9). From the leaf-EDVs,
SyncFinder prunes out non-shared variables (95% of leaf-
EDVs), and applies loop-variant based pruning, which fur-
ther prunes 80% of shared leaf-EDVs. SyncFinder then
applies the final pruning step to prune out sync variables
that are associated with condvar loops. The remains are
sync variable candidates and those loops using them are
potential sync loops.
5.3. Synchronization Pairing and Pruning
During synchronization pairing, SyncFinder applies two
pruning schemes, unsatisfiable remote update pruning and
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) 173
174
Apps. Initial w/ Remote w/ Serial With True
pairs update pr. pair pr. both pairs
[ Apache [| 27 [2 | 27 | 2 | 2
[Myson] 331 [ 208 eat
[Open DAP [[ 168 [__13¢__[_146_ [113 [_ 96
[ Ppzip2 [9 [1s ff 9 9
Table 10: False synchronization pair pruning. Note that the
numbers shown here are synchronization pairs. In all the other
results, we show “synchronization loops” (regardless how many
setting statements for an ad hoc sync loop)
serial pair pruning. Table 10 shows the effect of those
pruning steps on the same set of server/desktop applica-
tions in Table 9. First, remote update based pruning elimi-
nates 51.8% of false sync pair candidates on average. It is
especially effective on Apache, since the majority of sync
writes are just simple assignments with constant values, so
it is easy to determine whether such values would satisfy
the corresponding sync exit conditions.
Second, the effectiveness of serial pair pruning depends
on application characteristics. While it prunes out almost
all false positives in simple desktop/scientific programs
(e.g., PBZip2), it is less effective in servers like Apache,
where many function pointers are used. Due to the limita-
tion of function pointer analysis, it is hard to know in all
cases whether two certain regions cannot be concurrent.
To be conservative, SyncFinder does not prune the pairs
inside such regions. Fortunately, the remote update based
pruning helps filtering them out.
5.4 Two Use Cases: Bug Detection
[Apps [| Deadlock (New)
[Apache [[ 10) | 1 _]
PMysOL[[_ 2) J
[ Mozilla_[[ 2)
Table 11: Deadlock and bad practice detection
Table 11 shows that our simple deadlock detector (leverag-
ing SyncFinder’s ad hoc synchronization annotation) de-
tects five deadlocks involving ad hoc order synchroniza-
tions, including those shown in Figure 2 and Figure 3.
Previous tools would fail to detect these bugs since they
cannot recognize ad hoc synchronizations. Besides dead-
locks, our detector also reports 16 bad practices, i.e. wait-
ing in a sync loop while holding a lock, which could raise
performance issues or cause future deadlocks.
[Apache [| _30__| 7 | 8% |
[Mysqr [25 | [0%]
[OpenDAP [7 | 4 | 4%]
[Water [79 [ar 86%]
Table 12: False positive reduction in Valgrind
Table 12 shows that SyncFinder auto-annotation could
reduce the false positive rates of Valgrind data race detec-
tor by 43-86%.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
6 Related Work
Spin and hang detection Some recent work has been
proposed in detecting simple spinning-based synchroniza-
tions [32, 25, 18]. For example, [25] proposed some new
hardware buffers to detect spinning loops on-the-fly. [18]
also provides similar capability but does it in software.
Both can detect only simple spinning loops, i.e. those sync
loops with only one single exit condition and also directly
depend on sync variables (referred as “sc-dir’ in Table 3
in Section 2). As shown in Table 3 such simple spinning
loops account for less than 16% of ad hoc sync loops on
average in server/desktop applications we studied.
Besides, both of them are dynamic approaches and
thereby suffer from the coverage limitation of all dy-
namic approaches (discussed in Section 3). In contrast,
SyncFinder uses a static approach and can detect various
types of ad hoc synchronizations. Additionally, we also
conduct an ad hoc synchronization characteristic study.
Synchronization annotation Many annotation lan-
guages [4, 2, 1, 41] have been proposed for synchroniza-
tions in concurrent programs. Unfortunately, annotation
is not frequently used by programmers since it is tedious.
SyncFinder is complementary to these work by providing
automatic annotation for ad hoc synchronizations.
Concurrent bug detection tools Much research has been
conducted on concurrency bug detection [47, 20, 31, 6,
17, 11, 43]. These tools usually assume that they can
recognize all synchronizations in target programs. As we
demonstrated using deadlock detection and race detection,
SyncFinder can help these tools improve their effective-
ness and accuracy by automatically annotating ad hoc syn-
chronizations that are hard for them to recognize.
Transactional memory Various transactional memory
designs have been proposed to solve the programmability
issues related to mutexes [39, 30, 19, 44] and also con-
dition variables [10]. Our study complements such work
by providing ad hoc synchronization characteristics in real
world applications.
Software bug characteristics studies Several studies
have been conducted on the characteristics of software
bugs [8, 42, 34], including one of our own [26] on con-
currency bug characteristics. This paper is different from
those studies by focusing on ad hoc synchronizations in-
stead of bugs, even though many of them are prone to in-
troducing bugs. The purpose of this paper is to raise the
awareness of ad hoc synchronizations, and to warn pro-
grammers to avoid them when possible. Also we devel-
oped an effective way to automatically identify those ad
hoc synchronizations in large software.
7 Conclusions and Limitations
In this paper, we provided a quantitative characteristics
study of ad hoc synchronization in concurrent programs
and built a tool called SyncFinder to automatically identify
USENIX Association
USENIX Association
and annotate them. By examining 229 ad hoc synchroniza-
tion loops from 12 concurrent programs, we have found
several interesting and alarming characteristics. Among
them, the most important results include: all concurrent
programs have used ad hoc synchronizations and their im-
plementations are very diverse and hard to recognize man-
ually. Moreover, a large percentage (22-67%) of ad hoc
loops in these applications have introduced bugs or perfor-
mance issues. They also greatly impact the accuracy and
effectiveness of bug detection and performance profiling
tools. In an effort to detect these ad hoc synchronizations,
we developed SyncFinder, a tool that successfully identi-
fies 96% of ad hoc synchronization loops with a 6% false
positive rate. SyncFinder helps detect deadlocks missed
by conventional deadlock detection and also reduce data
race detector’s false positives. Many other tools and re-
search projects can also benefit from SyncFinder. For ex-
ample, concurrency testing tools (e.g., CHESS [31]) can
leverage SyncFinder’s auto-annotation to force a context
switch inside an ad hoc sync loop to expose concurrency
bugs. Similarly, performance tools can be extended to pro-
file ad hoc synchronization behavior.
All work has limitations, and ours is no exception: (i)
SyncFinder requires source code. However, this may not
significantly limit SyncFinder’s applicability since it is
more likely to be used by programmers instead of end
users. (ii) Due to some implementation issues, SyncFinder
still misses 1-3 ad hoc synchronizations. Eliminating
them would require further enhancement to some of our
analysis (such as alias analysis, etc.) (iii) Even though
SyncFinder’s false positive rates are quite low, for some
use cases that are sensitive to false positives, program-
mers would need to manually examine the identified ad
hoc synchronization or leverage some execution synthesis
tools like ESD [49] to help identify false positives. (iv) For
our characteristic study, we can always study a few more
applications, especially of different types.
8 Acknowledgments
We would like to express our deepest appreciation to our
shepherd, Professor George Candea, who was very re-
sponsive during our interactions with him and provided
us with valuable suggestions, which have significantly im-
proved our paper and strengthened our work. Moreover,
we would also like to thank the anonymous reviewers
whose comments and insightful suggestions have greatly
shaped and improved our paper and have taught us many
important lessons. Finally, we greatly appreciate Bob
Kuhn, Matthew Frank and Paul Petersen for their con-
tinuous support and encouragement throughout the whole
project, as well as their insightful feedback on the project
and the paper. This work is supported by NSF-CSR
1017784, NSF CNS-0720743 grant, NSF CCF-1017804
grant, NSF CNS-1001158 (career award) and Intel Grant.
References
[1] Java annotations. http://java.sun.com/j2se/1.5.0/
docs/guide/language/annotations.html.
[2] Lock_Lint - Static data race and deadlock detection tool
for C. http: //developers.sun.com/sunstudio/
articles/locklint.html.
[3] Miscompiled volatile-qualified variables. https://www.
securecoding.cert.org/confluence/display/
seccode/DCL17-C.+Bewaretof+miscompiled+
volatile-qualified+variables.
[4] MSDN run-time library reference - SAL annotations.
http://msdn2.microsoft.com/en-us/library/
ms235402.aspx.
[5] BOEHM, H.-J., AND ADVE, S. V. Foundations of the c++ con-
currency memory model. In PLDI ’08: Proceedings of the 2008
ACM SIGPLAN conference on Programming language design and
implementation (New York, NY, USA, 2008), ACM, pp. 68-78.
[6] BRON, A., FARCHI, E., MAGID, Y., NIR, Y., AND UR, S. Ap-
plications of synchronization coverage. In Proceedings of the ACM
SIGPLAN Symposium on Principles and Practice of Parallel Pro-
gramming, PPOPP 2005, June 15-17, 2005, Chicago, IL, USA
(2005), ACM, pp. 206-212.
[7] BURNS, B., GRIMALDI, K., KOSTADINOV, A., BERGER, E. D.,
AND CORNER, M. D. Flux: A language for programming high-
performance servers. In USENIX Annual Technical Conference,
General Track (2006), USENIX, pp. 129-142.
[8] CHou, A., YANG, J., CHELF, B., HALLEM, S., AND ENGLER,
D. R. An empirical study of operating system errors. In Pro-
ceedings of the 18th ACM Symposium on Operating System Prin-
ciples (18th SOSP’01) (Banff, Alberta, Canada, Oct. 2001), ACM
SIGOPS, pp. 73-88.
[9] CONDIT, J., HARREN, M., ANDERSON, Z. R., GAy, D., AND
NECULA, G. C. Dependent types for low-level programming. In
Programming Languages and Systems, 16th European Symposium
on Programming, ESOP 2007 (2007), Springer, pp. 520-535.
[10] DUDNIK, P., AND M.SWIFT, M. Condition variables and transac-
tional memory: Problem or oppertunity? In 4th ACM SIGPLAN
Workshop on Transactional Computing(Transact).
[11] ENGLER, D., AND ASHCRAFT, K. RacerX: effective, static de-
tection of race conditions and deadlocks. In Proceedings of the
nineteenth ACM symposium on Operating systems principles (Oct.
19-22 2003), pp. 237-252.
[12] FLANAGAN, C., AND FREUND, S. N. Atomizer: a dynamic atom-
icity checker for multithreaded programs. In POPL ’04: Proceed-
ings of the 31st ACM SIGPLAN-SIGACT symposium on Principles
of programming languages (New York, NY, USA, 2004), ACM,
pp. 256-267.
[13] HERLIHY, M., AND Moss, J. E. B. Transactional memory: Ar-
chitectural support for lock-free data structures. In JSCA (1993),
pp. 289-300.
[14] Hoare, C. A. R. Monitors: an operating system structuring con-
cept. Communications of the ACM 17 (1974), 549-557.
[15] Horwitz, S., REPS, T. W., AND BRINKLEY, D. Interprocedu-
ral slicing using dependence graphs. In Proceedings of the ACM
SIGPLAN’ S8 Conference on Programming Language Design and
Implementation (PLDI) (Atlanta, Georgia, 1988), pp. 35-46.
[16] HOwARD, J. H. Proving monitors. Commun. ACM 19, 5 (1976),
273-279.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) —-175
176
[17]
[18]
[19]
[20]
[21]
[22]
[23]
[24]
[25]
[26]
[27]
[28]
[29]
[30]
[31]
[32]
INTEL CORPORATION. Intel thread checker.
//software.intel.com/en-us/articles/
intel-thread-checker-documentation/.
JANNESARI, A., AND TICHY, W. F. Identifying ad-hoc synchro-
nization for enhanced race detection. In /PDPS (April 2010), IEEE.
JAYARAM BOBBA, NEELAM GOYAL, M. H.-M. S. D. W. To-
kentm: Efficient execution of large transactions with hardware
transactional memory. In International Symposium on Computer
Architecture (June 2008), pp. 127-138.
JULA, H., TRALAMAZZA, D. M., ZAMFIR, C., AND CANDEA,
G. Deadlock immunity: Enabling systems to defend against dead-
locks. In 8th USENIX Symposium on Operating Systems De-
sign and Implementation, OSDI 2008, December 8-10, 2008, San
Diego, California, USA (2008), pp. 295-308.
LAMPORT, L. Time, clocks, and the ordering of events in a dis-
tributed system. Commun. ACM 21, 7 (1978), 558-565.
LAMPSON, B., AND REDELL, D. Experience with processes and
monitors in mesa. Communications of the ACM 23, 2 (Feb. 1980),
105-117.
LATTNER, C., AND ADVE, V. LLVM: A Compilation Framework
for Lifelong Program Analysis & Transformation. In Proceedings
of the 2004 International Symposium on Code Generation and Op-
timization (CGO’04) (Palo Alto, California, Mar 2004).
LI, T., ELLIS, C. S., LEBECK, A. R., AND SORIN, D. J. Pulse:
A dynamic deadlock detection mechanism using speculative exe-
cution. In USENIX Annual Technical Conference, General Track
(2005), pp. 31-44.
LI, T., LEBECK, A. R., AND SORIN, D. J. Spin detection hard-
ware for improved management of multithreaded systems. [EEE
Transactions on Parallel and Distributed Systems PDS-17, 6 (June
2006), 508-521.
Lu, S., PARK, S., SEO, E., AND ZHOU, Y. Learning from mis-
takes — a comprehensive study on real world concurrency bug
characteristics. In Proceedings of the International Conference on
Architecture Support for Programming Languages and Operating
Systems (March 2008).
Lu, S., TUCEK, J., QIN, F., AND ZHOU, Y. Avio: detecting atom-
icity violations via access interleaving invariants. In ASPLOS-XII:
Proceedings of the 12th international conference on Architectural
support for programming languages and operating systems (New
York, NY, USA, 2006), ACM, pp. 37-48.
MANSON, J., PUGH, W., AND V.ADVE, S. The java memory
model. In POPL (January 2005), ACM.
MILLER, B. P., CALLAGHAN, M. D., CARGILLE, J. M.,
HOLLINGSWORTH, J. K., IRVIN, R. B., KARAVANIC, K. L.,
KUNCHITHAPADAM, K., AND NEWHALL, T. The paradyn par-
allel performance measurement tool. Special issue on performance
evaluation tools for parallel and distributed computer systems 28
(November 1995), 37-46.
MINH, C. C., TRAUTMANN, M., CHUNG, J., MCDONALD, A.,
BRONSON, N., CASPER, J., KOZYRAKIS, C., AND OLUKOTUN,
K. An effective hybrid transactional memory system with strong
isolation guarantees. In [SCA ’07: Proceedings of the 34th annual
international symposium on Computer architecture (New York,
NY, USA, 2007), ACM, pp. 69-80.
MUSUVATHI, M., QADEER, S., BALL, T., BASLER, G.,
NAINAR, P. A., AND NEAMTIU, I. Finding and reproducing
heisenbugs in concurrent programs. In 8th USENIX Symposium
on Operating Systems Design and Implementation, OSDI 2008,
December 8-10, 2008, San Diego, California, USA, Proceedings
(2008), USENIX Association, pp. 267-280.
NAKKA, N., SAGGESE, G. P., KALBARCZYK, Z., AND IYER,
R. An architectural framework for detecting process hangs/crashes.
In EDCC: EDCC, European Dependable Computing Conference
(2005).
http:
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
[33]
[34]
[35]
[36]
[37]
[38]
[39]
[40]
[41]
[42]
[43]
[44]
[45]
[46]
[47]
[48]
[49]
[50]
NETHERCOTE, N., AND SEWARD, J. Valgrind: a framework for
heavyweight dynamic binary instrumentation. SIGPLAN Not. 42,
6 (2007), 89-100.
OSTRAND, T. J., WEYUKER, E. J., AND BELL, R. M. Predicting
the location and number of faults in large software systems. IEEE
Trans. Software Eng 31, 4 (2005), 340-355.
PADIOLEAU, Y., TAN, L., AND ZHOU, Y. Listening to program-
mers taxonomies and characteristics of comments in operating sys-
tem code. In ICSE ’09: Proceedings of the 31st International Con-
ference on Software Engineering (Washington, DC, USA, 2009),
IEEE Computer Society, pp. 331-341.
PARK, S., LU, S., AND ZHOU, Y. CTrigger: exposing atomicity
violation bugs from their hiding places. In Proceedings of the 14th
International Conference on Architectural Support for Program-
ming Languages and Operating Systems, ASPLOS 2009, Washing-
ton, DC, USA, March 7-11, 2009 (2009), ACM, pp. 25-36.
RAJWAR, R., AND HILL, M. Transactional memory bibliography.
http: //www.cs.wisc.edu/trans-memory/biblio/.
REPS, T., AND ROSAY, G. Precise interprocedural chopping. In
SIGSOFT ’95: Proceedings of the 3rd ACM SIGSOFT symposium
on Foundations of software engineering (New York, NY, USA,
1995), ACM, pp. 41-52.
ROSSBACH, C. J., HOFMANN, O. S., PORTER, D. E., RA-
MADAN, H. E., ADITYA, B., AND WITCHEL, E. Txlinux: us-
ing and managing hardware transactional memory in an operating
system. In Proceedings of the 21st ACM Symposium on Operat-
ing Systems Principles 2007, SOSP 2007, Stevenson, Washington,
USA, October 14-17, 2007 (2007), ACM, pp. 87-102.
SHAVIT, N., AND TOUITOU, D. Software transactional mem-
ory. In Symposium on Principles of Distributed Computing (1995),
ACM Press.
STERLING, N. WARLOCK - A static data race analysis tool. In
USENIX Winter Technical Conference (1993), pp. 97-106.
SULLIVAN, M., AND CHILLAREGE, R. A comparison of software
defects in database management systems and operating systems. In
FTCS (1992), pp. 475-484.
SUN MICROSYSTEMS INC. Thread analyzer user’s guide. http:
//dlc.sun.com/pdf/820-0619/820-0619.pdf.
TIM HARRIS, SIMON MARLOW, S. P.-J. M. H. Composable
memory transactions. In ACM SIGPLAN symposium on Principles
and practice of parallel programming (2005).
V.ADVE, S., AND GHARACHORLOO, K. Shared memory consis-
tency models: A tutorial. In computer (1996), IEEE.
VAZIRI, M., TIP, F., AND DOLBY, J. Associating synchronization
constraints with data in an object-oriented language. In POPL ’06:
Conference record of the 33rd ACM SIGPLAN-SIGACT symposium
on Principles of programming languages (New York, NY, USA,
2006), ACM, pp. 334-345.
WANG, Y., KELLY, T., KUDLUR, M., LAFORTUNE, S., AND
MAHLKE, S. A. Gadara: Dynamic deadlock avoidance for mul-
tithreaded programs. In 8th USENIX Symposium on Operating
Systems Design and Implementation, OSDI 2008, December 8-10,
2008, San Diego, California, USA (2008), pp. 281-294.
WEISER, M. Program slicing. In JCSE ’8/: Proceedings of the 5th
international conference on Software engineering (Piscataway, NJ,
USA, 1981), IEEE Press, pp. 439-449.
ZAMBFIR, C., AND CANDEA, G. Execution synthesis: a technique
for automated software debugging. In EuroSys ’10: Proceedings
of the Sth European conference on Computer systems (New York,
NY, USA, 2010), ACM, pp. 321-334.
ZHOU, F., CONDIT, J., ANDERSON, Z. R., BAGRAK, I., EN-
NALS, R., HARREN, M., NECULA, G. C., AND BREWER, E. A.
Safedrive: Safe and recoverable extensions using language-based
techniques. In OSDI (2006), pp. 45-60.
USENIX Association
USENIX Association
Deterministic Process Groups in dOS
Tom Bergan Nicholas Hunt
Luis Ceze Steven D. Gribble
Department of Computer Science & Engineering, University of Washington
Abstract
Current multiprocessor systems execute parallel and concurrent
software nondeterministically: even when given precisely the
same input, two executions of the same program may produce
different output. This severely complicates debugging, testing,
and automatic replication for fault-tolerance. Previous efforts to
address this issue have focused primarily on record and replay,
but making execution actually deterministic would address the
problem at the root.
Our goals in this work are twofold: (1) to provide fully de-
terministic execution of arbitrary, unmodified, multithreaded
programs as an OS service; and (2) to make all sources of in-
tentional nondeterminism, such as network I/O, be explicit and
controllable. To this end we propose a new OS abstraction, the
Deterministic Process Group (DPG). All communication be-
tween threads and processes internal to a DPG happens de-
terministically, including implicit communication via shared-
memory accesses, as well as communication via OS channels
such as pipes, signals, and the filesystem. To deal with funda-
mentally nondeterministic external events, our abstraction in-
cludes the shim layer, a programmable interface that interposes
on all interaction between a DPG and the external world, mak-
ing determinism useful even for reactive applications.
We implemented the DPG abstraction as an extension to
Linux and demonstrate its benefits with three use cases: plain
deterministic execution; replicated execution; and record and
replay by logging just external input. We evaluated our imple-
mentation on both parallel and reactive workloads, including
Apache, Chromium, and PARSEC.
1. Introduction
Nondeterminism makes the development of parallel and
concurrent software substantially more difficult. Soft-
ware testers face daunting incompleteness challenges be-
cause nondeterminism leads to an exponential explo-
sion in possible executions [27]. Developers must rea-
son about large sets of possible behaviors and attempt to
debug without precise repeatability [31, 36]. Moreover,
standard techniques for fault-tolerant replication do not
work when the software being replicated executes nonde-
terministically [38]. At the same time, the growing pop-
ularity of multicore architectures is making parallel and
concurrent software more and more important.
Unfortunately, nondeterminism is pervasive; thread
scheduling, memory reordering, and timing variations
at the hardware level can all affect the interleaving of
threads and cause a multithreaded program to produce
different outputs when given the same input. We define
this as internal nondeterminism. Internal nondetermin-
ism is entirely hidden from the programmer and thus is
undesirable. However, as we demonstrate in this paper, it
is not fundamental and can be completely removed. On
the other hand, events such as user input and the arrival
of network packets are triggered nondeterministically by
the external world. We define this as external nondeter-
minism; this kind of nondeterminism, if present, is fun-
damental and cannot be removed.
What we want is a software environment where in-
ternal nondeterminism is completely eliminated. What
we want is more than just deterministic record and re-
play: multithreaded programs should always execute de-
terministically relative to their explicitly specified in-
puts. Moreover, where external nondeterminism exists,
it should be made explicit and controllable.
Recent research has begun to explore ways of reduc-
ing internal nondeterminism in multithreaded programs.
However, current proposals fall short in several aspects:
they do not deal with nondeterministic channels other
than shared-memory; they do not offer ways of making
external nondeterminism explicit and controllable; they
either require new hardware [14], apply to only a sub-
set of programs [7, 29], or require recompilation [6]; and
they do not support multiprocess applications.
Our goals are to completely eliminate nondetermin-
ism where possible, including channels beyond shared-
memory like pipes, signals, and the filesystem, and to
make all intentional, external nondeterminism explicit
and controllable. To this end, we propose a new OS
abstraction, the Deterministic Process Group (DPG). A
programmer uses this abstraction to define a determinis-
tic box inside which all communication happens deter-
ministically. All of the nondeterministic input received
by a DPG is interposed upon by the shim layer, an in-
terface that can be used by programmers to observe and
control external nondeterminism in a flexible way.
A DPG is effectively a high-level deterministic vir-
tual machine. The deterministic guarantees are provided
transparently by the OS without intervention from the
programmer; thus, DPGs can host arbitrary, unmodified
application binaries. At any given time there may be
many DPGs running alongside many conventional non-
deterministic processes. An alternative design is full-
system determinism, in which a hypervisor executes an
entire OS deterministically relative to inputs triggered by
the hardware. The DPG approach is more flexible be-
cause the programmer can select the desired granularity
of determinism for each individual application.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) —:177
178
1.1 DPG Use Cases
Debugging and Testing Many applications do not con-
tinuously interact with the external world, but instead
read inputs at deterministic points in their execution.
Since DPGs provide internal determinism by default,
these applications will execute completely deterministi-
cally when run within a DPG. This has obvious bene-
fits for debugging, since execution is directly repeatable.
Moreover, removing internal nondeterminism has the po-
tential to reduce the problem of testing multithreaded
programs to the problem of testing sequential programs
by making execution a function of only the explicit in-
puts, including external nondeterminism.
Record/Replay Controlling external nondeterminism
with the shim layer makes determinism useful even for
applications that interact continuously with the external
world. As an example, one can run an application in-
side a DPG and extend the shim layer to log all exter-
nal nondeterminism. This log can be used later to faith-
fully replay an application’s execution for debugging and
other analyses. Most prior work on record and replay
of multithreaded applications focuses on how to record
internal nondeterminism caused by shared-memory ac-
cesses. This leads to either unwieldy logs and high over-
heads [16, 22] or imprecise replay [1, 31, 36]. The inter-
nal determinism offered by DPGs completely subsumes
this problem; only external inputs need to be recorded.
Replication for Fault Tolerance DPGs naturally en-
able replication of multithreaded applications. By run-
ning multiple copies of an application inside DPGs on
several machines and replicating the inputs, all replicas
will behave the same way because there is no internal
nondeterminism. This can be implemented by extending
the shim layer to ensure that all replicas receive the same
input at the same point in their execution. Because DPGs
eliminate all forms of internal nondeterminism, there is
less to log and replicate. This is a major issue in prior
work [5, 38-40] on replication mostly because shared-
memory is a very large source of such nondeterminism.
1.2. Outline and Contributions
This paper makes several conceptual and architectural
contributions. First, we identify the fundamental dis-
tinction between internal and external nondeterminism,
and we demonstrate that internal nondeterminism can be
eliminated from programs. To do this, we expand on ear-
lier work that removed shared-memory nondeterminism
by also removing internal nondeterminism from signals,
pipes, the filesystem, and other OS channels.
Second, we propose the Deterministic Process Group
abstraction (Section 2), which lets programmers define
the boundary between internal and external nondeter-
minism. As part of this abstraction we introduce the
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
shim layer, whose interface lets programmers observe
and control all external nondeterminism.
This paper also presents and evaluates our implemen-
tation of these ideas. In Sections 3-4, we describe dOS, a
Linux-based implementation of DPGs and the shim layer
that enables the deterministic execution of arbitrary, un-
modified binaries. Section 5 demonstrates the usefulness
of the shim layer by using it to implement determinis-
tic filesystem services, replicated execution of a multi-
threaded server, and record/replay. Section 6 provides a
detailed evaluation of dOS and our shim applications on
a variety of workloads. Finally, we end with related work
and closing remarks.
2. The Abstraction
Figure | illustrates the abstract model of a Determin-
istic Process Group and Figure 2 illustrates the major
components of our system. A DPG consists of a group
of threads and processes along with the kernel objects
they share. Kernel objects include shared-memory pages,
pipes, and sockets. Threads communicate by performing
operations on shared kernel objects, for example by read-
ing from a shared page or writing to a shared pipe. A ker-
nel object is internal if it can be modified only by threads
inside the DPG, and is external if it can be modified by
threads or devices outside the DPG. We refer to a thread
executing inside a DPG as a deterministic thread, and we
refer to a DPG’s set of threads and internal objects col-
lectively as a deterministic box.
Figure | shows three deterministic threads, Thread,,
Threadgz, and Threads3, two internal objects, the memory
page and the pipe, and two external objects, the socket
and the file. Thread, and Threadz are members of
the same process, P,. The deterministic box is illustrated
with a dotted outline. Note that internal objects need not
be shared by the entire DPG; in this example, the memory
page is shared by just two threads.
The final component of a DPG is a user-space ser-
vice called a shim program. A shim program sits on the
boundary of a deterministic box, and its job is to in-
terpose on communication that crosses the deterministic
boundary. Shim programs are written using a system call
interface called the shim layer. This interface provides
new opportunities for systems programmers that we ex-
plore in detail throughout this paper.
2.1 DPGs and Their Guarantees
A new DPG is created with the sys_makedet system
call, and initially hosts just the calling thread. Each new
thread spawned by the initial thread is added to the DPG,
and in this way the DPG expands to include all descen-
dant threads and processes. A thread leaves a DPG when
it exits. We have not found DPG join and leave prim-
itives necessary and so have not defined them. Threads
USENIX Association
USENIX Association
' DPG P
' i ' o
' | 2
! 7] &
[| Threads
' P 3
1
=
Thread3 | a
"oo
Figure 1. A Deterministic Process Group
hosted in a DPG need not share an address space, which
means a DPG can host many multithreaded processes.
Deterministic threads invoke system calls and read
and write shared-memory just like ordinary threads.
However, DPGs distinguish between operations on in-
ternal objects, which happen deterministically, and oper-
ations on external objects, which happen nondetermin-
istically. Interactions with external objects represent a
DPG’s only source of nondeterminism; essentially, these
external interactions represent the inputs a DPG receives
from the external world.
Given the same initial state and the same stream of
external inputs, a DPG is guaranteed to execute the
same steps of inter-thread communication and produce
the same output. More precisely, as a DPG executes it
performs shared-memory loads and stores, invokes
system calls, and handles asynchronous signals; each of
these operations introduces nondeterminism only when
it involves an entity outside the DPG. This is a stronger
guarantee than output determinism [1, 23], which guar-
antees that replaying a program will produce the same
output, but not that it will reproduce all inter-thread com-
munication steps that lead to that output.
For example, when operating on a network socket, the
read system call returns nondeterministic data. Addi-
tionally, read is a blocking call; it does not return un-
til data is available, which means read will block for a
nondeterministic amount of time. However, when read
operates on a device that is internal to a DPG, such as an
internal pipe, read behaves deterministically.
In summary, a DPG experiences nondeterminism only
when it: (1) reads data from an external source; (2)
blocks to wait for external data; or (3) handles a sig-
nal sent from an external source. Our guarantee is that
DPGs execute deterministically relative to a stream of
such nondeterministic input, and also relative to the ini-
tial state of the DPG at the call to sys_makedet. Note
that this guarantee holds even across different machines.
Logical time Conceptually, a DPG executes as if it was
serialized onto a logical timeline, where logical time is
represented by a single global counter. Blocking system
calls occupy two points on the logical timeline, one to
initiate the call and the other to complete the call. dOS
ensures that internal communication is mapped onto the
logical timeline in a deterministic way. (Section 3 de-
Shim
ae Program
user
kernel
Operating System
Figure 2. System Overview
void shim_attach(tid, SYS|SIG)
void shim_trace(*event)
void shim_resume(tid, result)
void shim_queue_sig(tid, siginfo)
void shim_ctl(tid, ...)
(a) Interposing on Nondeterminism
void shim_sleep (tid)
void shim_add_barrier (tid, logical_time)
int shim_gettime (tid)
(b) Controlling Logical Time
Figure 3. Shim layer system calls
scribes how our implementation groups instructions into
atomic epochs in order to extract parallelism.) Note that
logical time and physical time are distinct: DPGs guar-
antee deterministic output, but not deterministic perfor-
mance. Input from the external world is mapped onto the
logical timeline in a way controlled by the shim layer,
which is the subject of the next section.
2.2 The Shim Layer
Every DPG is monitored by a user-space service called a
shim program, also referred to as a shim. Shim programs
use the shim layer interface (Figure 3) to observe and
control nondeterministic input.
At a high level, there are two kinds of nondetermin-
istic input: the what and the when. The what includes
the values of external input, such as data read from the
network. Then when includes the blocking times of non-
deterministic system calls, as well as the delivery times
of external signals. Shims can observe and control both
kinds of nondeterministic input.
As a motivating example, consider record and replay
implemented with a pair of shim programs. The record
shim observes execution: for every nondeterministic sys-
tem call, the shim logs the number of logical time steps
the call spent blocked, along with the return value of the
call. The replay shim controls execution: it ensures that
every nondeterministic system call is scheduled to return
at the specific logical time and with the specific value
specified in the log.
The following sections first describe how shims ob-
serve and control the what (Figure 3a), and then how
shims observe and control the when (Figure 3b), using
record and replay as running examples.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) —-179
180
DPG's DPG Thread
logical time
: —-
(a) system call
Shim Program
trace
(wait for event)
t+1
t+2
forward call
(c)
add_barrier (now)
trace
(sleeping)
barrier reached (watt ot
saa due Ree (d)
t+n resume
continues
Figure 4. Observing a blocking system call
2.2.1 Interposing on Nondeterminism
Shims use shim_trace to wait for a DPG to encounter
nondeterminism. shim_trace blocks until either (a) a
deterministic thread is about to perform a nondeterminis-
tic system call, or (b) an external signal is about to be de-
livered to a deterministic thread. In both cases, the deter-
ministic thread stalls, execution transfers to the shim pro-
gram, and shim_trace returns. The shim can interpose
on this nondeterministic event and then return control
back to the deterministic thread by calling shim_resume.
In this way, execution of a deterministic thread alternates
between itself and a shim program, much like execution
of an ordinary thread alternates between user-space and
kernel-space.
For system calls, shim trace populates the given
event structure with the system call number and argu-
ments. The shim should perform the system call on be-
half of the deterministic thread and then transfer control
back to deterministic thread by calling shim_resume, us-
ing the result parameter of shim_resume to specify the
system call’s return value. The shim might perform the
call by forwarding the call to the OS (e.g., for record) or
by ignoring the OS entirely (e.g., for replay).
For external signals, the event structure includes the
siginfo_t of the pending signal. The shim can queue
the signal for delivery by calling shim_queue-_sig, save
the signal internally for later delivery, or discard the
signal entirely. In each case, the shim returns control to
the deterministic thread by calling shim_resume with an
empty result.
2.2.2 Controlling Logical Time
A shim program monitors the passage of logical time
in a DPG by registering logical time barriers using
shim_add_barrier. A logical time barrier is a timer tied
to a specific deterministic thread (through the tid param-
eter); when the timer goes off, the deterministic thread
stalls and the shim is notified through shim_trace. The
barrier time is specified as an offset relative to the cur-
rent logical time of the DPG, which can be obtained with
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
DPG's DPG Thread
logical time
| —-
(a) system call
Shim Program
trace
(wait for event)
add_barrier (n)
Seeusna: a (b)
il perform call
t+2 :
(sleeping) trace
(wait for barrier)
(c) barrier reached
a or ae
continues
Figure 5. Controlling a blocking system call
shim_gettime. Time barriers can be used to control the
nondeterministic when, as described below.
System Call Blocking Time Figures 4 and 5 illustrate
how to observe and control the number of logical time
steps that a system call blocks. Both examples follow a
similar pattern; the only difference is the way in which
shim_add_barrier is called.
Figures 4 illustrates observing a blocking system call
(e.g., for record). When deterministic thread T’ performs
a system call (a), the call is trapped by the shim, which
returns from shim_trace. At this point, thread T stalls
and the DPG’s logical time does not advance. The shim
can now forward the call to the OS, but before doing
so it puts T’ to sleep by calling shim_sleep (b). While
T is asleep it is detached from the logical timeline and
does not execute; this allows a nondeterministic amount
of logical time to pass in the DPG while the system call
is being performed. When the system call finally com-
pletes, the shim synchronizes with the DPG by register-
ing a time barrier for T’ to happen at the very next logical
time step in the DPG (c). Once that barrier triggers, the
shim returns control to 7’ via shim_resume (d).
Figure 5 illustrates controlling a blocking system call
(e.g., for replay). Again, deterministic thread 7’ per-
forms a system call which is trapped by the shim via
shim_trace (a). The key difference in this example is
that the shim decides, a priori, that the system call should
complete in exactly n logical time steps. For example, a
replay shim would read n from a log. To enforce this,
the shim registers a barrier for T' that will trigger n steps
in the future and then puts T to sleep (b). While 7’ is
asleep it does not execute; the rest of the DPG executes
normally for exactly n logical time steps, but no further.
At this point the barrier triggers: T’ wakes and notifies
the shim (c). Finally, the shim returns from the system
call and returns control to thread T (d).
Signal Delivery Time Now suppose a shim wants to
deliver a signal to thread T' at logical time n. To do
this, the shim should simply register a barrier for time
n. When that barrier is reached, the shim can queue the
USENIX Association
USENIX Association
signal for immediate delivery using shim_queue_sig
and then resume the thread using shim_resume.
2.2.3. Shim Use Cases
Shim programs can implement the record/replay and
replicated execution services discussed earlier, but we
envision many other kinds of shim programs as well.
Some shims will be generic, application-independent
services written by systems programmers, while others
will be written by application programmers and tailored
to enhance a specific application. Additionally, a shim
program can be used to adjust the boundary of a deter-
ministic box in two ways described below.
Expanding the Set of Deterministic Services An OS
that supports DPGs may decide to implement some sys-
tem calls nondeterministically to reduce kernel complex-
ity, even when deterministic implementations are possi-
ble under the right assumptions. For example, in dOS,
interaction with local files remains nondeterministic due
to variations in disk latency, even though this nondeter-
minism can be considered internal and thus eliminated
under the right assumptions. Section 5.1 explores how a
shim can make local file access deterministic.
Further, a shim can virtualize global resources such
as process identifiers in a deterministic way, as in [30].
A shim can even convert physical times (e.g., used by
sleep and alarm) into virtual, logical times. This would
eliminate nondeterminism introduced by real time, but
of course is only meaningful for applications that do not
require a precise correspondence with real time.
Customizing the Nondeterministic Interface System
calls are a DPG’s basic interface to the nondeterministic
world. However, it is often beneficial to let applications
define the nondeterministic interface at a more abstract
level. For example, a server application might want to
hide many low-level read and write system calls be-
hind a single high-level, nondeterministic getmsg call.
Previous work has argued that this flexibility is valuable
for record/replay systems [19], but we consider this flex-
ibility to be even more general; for example, Section 5.3
shows how it is useful for replicated execution.
We enable this flexibility in dOS by defining a new
system call, dpg_callshim, which makes a direct call
from a DPG into its shim. Effectively, dpg_callshim
allows developers to divide an application into two parts:
the deterministic part that runs in a DPG and the nonde-
terministic part that runs in a shim.
3. Deterministic Execution Algorithm
The first implementation choice we make is which al-
gorithm dOS uses to enforce determinism. Prior work
on shared-memory determinism has proposed a family
of deterministic execution algorithms, including DMP-
O, DmP-B, and DMP-TM [6, 14]. dOS implements the
Parallel Mode Serial Mode ea
——45 waiting
: ——| quantum
7 (b) : (c) : boundary
1 . (a) ™ is ‘ a ‘round
: i i ' robin
T23.-——__ e ‘/+-_— ¥
T3; 4
time —> end of round
Figure 6. Timeline of a quantum round in DMp-O.
Ty) finishes its quantum in parallel mode (a), while T
and T3 have work left for serial mode (b,c).
DmMpP-O ownership-tracking algorithm; we selected it for
its relative simplicity of implementation, but any deter-
ministic execution algorithm can support DPGs as long
as the shim layer can be implemented on top of it.
One constraint imposed by the shim layer is that log-
ical time should be representable with a single global
counter. DMPp-O, DMP-B, and DMP-TM all satisfy this
constraint, but other (as yet uninvented) algorithms may
require a more complex notion of logical time, such as
a vector clock. We believe the shim layer could be ex-
tended to support such algorithms, but the details are left
for future work.
In the rest of this section, we first summarize our
earlier work on using DMP-O to enforce deterministic
execution of multithreaded programs that communicate
via shared-memory. Next, we describe how to generalize
DmpP-O to include communication via channels other
than shared-memory, such as pipes and signals.
3.1 Shared-Memory Determinism
Two key observations underlie DMP-O. First, if threads
do not touch shared data, i.e., if they do not commu-
nicate, their execution will be deterministic no matter
how they are scheduled. Second, when threads do com-
municate, a trivial deterministic schedule is to divide
each thread’s execution into chunks and then execute all
chunks in a deterministic serial order.
Following these observations, execution in DMP-O is
divided into chunks called quanta. A round consists of
all threads executing one quantum each. Each round is
divided into a parallel mode and a serial mode. In paral-
lel mode, threads run in parallel but are isolated; they do
not communicate. In serial mode, threads run serially but
can communicate arbitrarily. A thread ends its parallel
mode once it has reached an instruction that might com-
municate with other threads. Serial mode begins once all
threads have completed parallel mode, and ends once all
threads have had a chance to run. The parallel and serial
modes are thus isolated by global barriers into two-stage
rounds, as illustrated in Figure 6.
Notice that the parallel and serial modes are directly
inspired from the two key observations stated above.
DmP-O is deterministic as long as threads are (1) bro-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI’10) —181
182
ken into quanta at deterministic boundaries, (2) ordered
deterministically in serial mode, and (3) correctly iso-
lated in parallel mode. The first two constraints are easily
satisfied: we define a quantum to be some deterministic
number of dynamic instructions, and we order threads in
serial mode by sorting them by creation order.
Dmp-O achieves isolation in parallel mode by par-
titioning ownership of shared-memory across threads.
Each memory location is in one of two ownership states:
owned-by-T' for some thread T, or shared. A location
that is owned-by-T is private to T; no other thread can
access the location during parallel mode. A location that
is shared is globally read-only; all threads can read the
location during parallel mode, but none can modify it. A
thread waits for serial mode before performing an opera-
tion that does not meet these conditions.
Ownership states evolve during serial mode by fol-
lowing two rules: (1) before thread T writes to a location,
it sets ownership of that location to owned-by-T;; and (2)
before T reads a location that is not owned-by-T,, it sets
ownership of that location to shared.
Logical time Finally, we say that logical time incre-
ments on every mode transition, i.e., on every transition
from parallel mode to serial mode and back. Note that
within a single mode every thread appears to execute
atomically. From this property it follows that mode tran-
sitions are meaningful increments of logical time.
3.2 Beyond Shared-Memory Communication
Our model of a DPG from Figure 1 is that threads com-
municate by performing operations on shared kernel ob-
jects, which includes more than just shared-memory. To
generalize DMP-O to this model we first observe that
we can track ownership of shared kernel objects just as
for shared-memory locations: if an operation mutates a
kernel object it acts as a “write,’ while if an operation
only observes a kernel object it acts as a “read.” In fact,
our implementation (Section 4.1.1) tracks ownership of
shared-memory at the page granularity, effectively treat-
ing a memory page as just another kernel object.
To fully generalize DMP-O we need two additional
changes: the first deals with blocking operations, and the
second deals with asynchronously delivered signals.
Blocking Operations When a system call blocks, the
calling thread ends its current mode (either parallel mode
or serial mode) and is not scheduled to run again until it
unblocks. While a thread is blocked, the rest of the DPG
continues to execute. A thread can only unblock during
a mode transition; this ensures that threads unblock at
discrete points on the logical timeline.
Signal Delivery Incoming signals are queued during
the current mode then delivered immediately on the next
mode transition. Queued signals are partitioned into in-
ternal and external signals, depending on whether they
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
were sent from a thread inside or outside the DPG, re-
spectively. If there are N threads in a DPG then each
deterministic thread has N logical queues: one queue for
external signals and one queue for signals sent from each
of the NV — 1 other deterministic threads.
On a mode transition, internal signals are delivered
first and external signals are delivered last. The internal
signal queues are emptied in a deterministic order, e.g.,
by using the ID of the sending thread as a sort key.
This strategy ensures, first, that internal signals are de-
livered deterministically, and second, that external sig-
nals are delivered at meaningful logical times. Note that
when a thread sends a signal to itself (as with SIGSEGV)
the signal is synchronous; such signals are always deliv-
ered instantly. (OS implements the N-queue model de-
scribed here using a single sorted list. Additionally, dOS
always delivers external SIGKILL signals immediately
(rather than forwarding them to the shim) so that a DPG
can be killed even when its shim program misbehaves.
4. Linux-Based Implementation
We now describe how we implemented dOS, which is a
variant of Linux that implements the DPG abstraction.
dOS makes two major changes to Linux: first, it imple-
ments the shim layer; and second, it implements DMP-O,
which includes an object ownership-tracking mechanism
and a deterministic scheduler that constrains the execu-
tion of each DPG to a deterministic logical timeline. dOS
exports a traditional system call interface to DPGs along
with the sys_makedet and dpg_callshim system calls.
Our implementation of DMP-O was the most chal-
lenging and invasive change. Overall, we added roughly
5800 lines of new code to the Linux 2.6.24-7/x86-64 ker-
nel and changed roughly 2500 lines of existing code in 53
files. Below we summarize the low-level implementation
details of dOS and discuss engineering challenges (Sec-
tions 4.1-4.4). We end with a summary of the strengths
and limitations of our implementation (Section 4.5).
4.1. Ownership Tracking
4.1.1 Shared-Memory Pages
dOS tracks ownership of shared-memory at the page
granularity by using hardware page-protection to verify
that a deterministic thread does not access a page without
appropriate ownership.
Conceptually, dOS maintains a shadow page table
for each thread. A thread’s shadow table mirrors its real
page table exactly, except that shadow permission bits
are modified to reflect the current distribution of page
ownership. dOS exposes only the shadow page tables to
hardware: on a context switch to thread 7, dOS installs
T’s shadow table onto the CPU even if the previously
scheduled thread shared an address space with T’.
USENIX Association
USENIX Association
Page ownership is encoded into shadow page table
permissions so that ownership violations such as a store
to a shared page will trigger a page fault. dOS intercepts
this page fault, notices it is due to an ownership violation,
and stalls the faulting thread until it is scheduled to run
in serial mode. dOS then assigns ownership of the page
to the faulting thread and continues its execution.
Every conventional process has one real page table
representing its address space. All address space modifi-
cations are expressed in terms of the real page table and
then transparently applied to the shadows. To limit mem-
ory overheads, dOS maintains just N shadow tables per
address space, where N is the number of CPUs, and then
assigns threads to shadow tables, effectively bucketing
the threads in a given process into N ownership groups.
This requires a slight tweak to the DMP-O scheduler:
during parallel mode, all threads that share a shadow
table must be serialized in a deterministic order (e.g.,
scheduled serially in thread creation order). We bucket
threads using a simple greedy algorithm.
This strategy is not limited to shared-memory within
a single process. dOS supports shared-memory across
processes by tracking ownership of physical pages; we
use Linux’s rmap facility to enumerate all user-space
addresses that map a given physical page.
Finally, there are two comer cases worth mention-
ing. First, (OS disables address space randomization for
DPGs so that every DPG has a deterministic address
space layout. Note that we can enable address space ran-
domization in DPGs if we expose the seed as external
nondeterminism. Second, page swapping can introduce
nondeterministic changes to page tables. To preserve de-
terminism, when a page is swapped out, dOS preserves
the page’s ownership state using extra bits in the shadow
page tables. When a page fault triggers a swap-in, dOS
stalls the thread until the page is read from disk, and then
restores the saved ownership state of the page.
4.1.2 Other Kernel Objects
Other kernel objects, such as pipes and sockets, are op-
erated on by system calls. dOS instruments the kernel so
that a system call never operates on a kernel object unless
the calling thread has the appropriate level of ownership.
Adding this instrumentation presents two engineering
challenges. First, where should the instrumentation be
placed? It is tempting to lazily acquire ownership of an
object just before a system call actually uses the object,
but doing this requires reengineering kernel locking pro-
tocols. To see why, note that acquiring ownership may re-
quire sleeping the calling thread to wait for serial mode.
However, a system call may not decide to use an object
until inside an atomic region, e.g., while holding a spin
lock, and it is not safe to sleep in such regions.
dOS avoids this difficulty by conservatively acquiring
ownership of all objects a system call may use before
Behavior (Total Syscalls) Examples
use pages (14) mprotect, read
(6) mprotect, mmap, brk
use address space
use inode (32) read, write, lseek, close
use fd table (9) open, dup, close
use fs path (22) open, chdir, chroot, access
read untracked
modify untracked
(54) getpid, gettimeofday
(172) kill setrlimit,sigaction
Table 1. System call behaviors
executing the call. This requires adding instrumentation
in just two places: at the system call entry point, and in
the code that wakes up a thread. dOS instruments thread
wakeup to reacquire any privileges lost while the system
call was asleep, e.g., while waiting for input.
The second challenge is that Linux is a large, com-
plex system with over 250 system calls and many unique
types of kernel objects. To simplify our implementation,
we track a few kinds of kernel objects precisely and then
conservatively merge all other kinds of objects into an
untracked objects group. For all but the untracked ob-
jects, dOS tracks ownership using a hash table that maps
an object to its current owner. Freshly allocated objects
are initially owned-by the allocating thread. Ownership
of the untracked objects is implicit: during parallel mode
they are shared; and during serial mode they are owned-
by the thread currently running. Thus, read-only opera-
tions on untracked objects can execute in parallel mode,
while all other operations on untracked objects must wait
for serial mode. This strategy is summarized in Table 1.
An inode is Linux’s internal name for files, sockets,
pipes, and anything else that can be referenced by a file
descriptor. System calls like read that operate on file
descriptors can modify the contents of memory pages,
map new pages into the address space, or even modify the
inode itself. These system calls must acquire ownership
of all of these objects before proceeding.
4.2 Scheduling
The dOS scheduler is implemented as a filter in front of
the default Linux scheduler—it does not push a deter-
ministic thread into the Linux scheduler until the thread
has been scheduled to run by its DPG. This filter imple-
ments the DMP-O scheduling algorithm.
Thread Creation The fork and clone system calls al-
ways execute in serial mode. This ensures that determin-
istic threads are spawned in a global serial order. The
newly spawned thread will be scheduled to run during
the next parallel mode.
Logical Time Barriers The dOS scheduler checks for
pending time barriers on each mode transition. To pre-
vent deadlock, dOS instantly fast-forwards logical time
to the next pending time barrier whenever all threads in
a DPG are simultaneously asleep.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) —:183
184
Quantum Formation Recall that parallel mode ends
when all threads have either reached a quantum bound-
ary or stalled to acquire ownership, and serial mode
ends once all threads have reached a quantum boundary,
where quantum boundaries must occur at deterministic
points in a thread’s execution. A possible implementa-
tion is to mark quantum boundaries with system calls,
but this does not guarantee forward progress because
a thread may loop forever without making any system
calls. Additionally it does not guarantee balance; im-
balance leads to excessive waiting at the end of parallel
mode, which leads to poor performance [6].
To guarantee forward progress, dOS defines a quan-
tum budget, which is the maximum amount of work a
thread can perform in a quantum. dOS estimates work
by counting instructions. The quantum budget is simply
a deterministic number of instructions, typically in the
range of tens to hundreds of thousands of instructions.
dOS counts instructions using the hardware “instruc-
tions retired” counter that is available on all modern x86
CPUs. dOS configures this counter to trigger an overflow
interrupt after the quantum budget expires. There are
well-documented caveats about using this counter [15,
43]. Specifically, the counter suffers from nondetermin-
ism that can be engineered around. We follow the so-
lution outlined by [15]: to overcome imprecise interrupt
delivery, dOS must single-step the DPG (via the x86 trap
flag) for up to about 200 instructions per quantum, which
can introduce large overheads. To avoid those overheads,
as an optimization, dOS deterministically ends a quan-
tum when returning from a system call if the remaining
quantum budget is low, but not yet exhausted.
4.3 Additional Optimizations
As demonstrated in [6], DMpP-O performs best when par-
allel mode is balanced and when serial mode is empty.
dOS implements a few optimizations to bias execution
towards these conditions. dOS automatically adjusts a
DPG’s quantum budget: when dOS detects significant
parallel mode imbalance, the budget is decreased to re-
duce imbalance, and when dOS detects well-balanced
parallel modes, the budget is increased to reduce quan-
tum barrier overheads. To limit the time spent executing
in serial mode, dOS ends a quantum after a few (heuris-
tically determined) ownership transfers. All of these op-
timizations preserve determinism, since the parameters
used evolve deterministically.
4.4 Shim Programs
In concrete terms, a shim program is composed of a
collection of threads called shim threads. Shim threads
begin life as ordinary user-space threads, e.g., after being
spawned by fork or clone. An ordinary thread becomes
a shim thread by calling shim_attach to attach to some
deterministic thread 7’. Once attached to 7’, the shim
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
thread is the distinguished thread that will intercept all
of T’s nondeterminism through shim_trace. If the shim
thread crashes, 7’ will stall on external operations until
attached to by another shim thread.
A thread can act as a shim thread for more than one
deterministic thread. Additionally, to simplify the imple-
mentation of shims, a shim thread can elect to receive
only the nondeterministic system calls or only the exter-
nal signals for a given deterministic thread (by setting the
second parameter of shim_attach). Our usual strategy
is to spawn one shim process for every DPG. Within this
process we spawn one shim thread to intercept signals for
the entire DPG, and for every deterministic thread in the
DPG we spawn one shim thread to interpose on the sys-
tem calls performed by the corresponding DPG thread.
Intercepting System Calls When a shim program inter-
cepts a system call it has two options: (1) it can emulate
the system call completely; or (2) it can simply instru-
ment the system call’s entry and exit, allowing the deter-
ministic thread to actually execute the body of the system
call. These options resemble those allowed by ptrace.
The option to simply instrument a system call is se-
lected by passing a special result to shim_resume. This
option gives a shim limited control over how the system
call executes in logical time. For example, if a shim sim-
ply instruments read instead of emulating it, the shim
cannot observe or control when the kernel writes to the
given user-space buffer (the writes will happen nonde-
terministically, in an unrecordable way). We provide in-
strumentation as a convenience for cases where full em-
ulation is not necessary. During system call emulation, a
shim can use shim_ct1 to perform side effects in a DPG,
such as writing to or reading from a user-space buffer.
RDTSC_ dOS allows shim programs to interpose on the
nondeterministic RDTSC instruction. Our implementation
uses the time stamp disable flag of the x86 cr4 register
to fault on user-mode accesses to RDTSC; these events are
exposed to the shim via shim_trace.
4.5 Discussion
Guarantees Provided by dOS_ dOS guarantees that
communication via the following kernel objects is de-
terministic as long as the objects are completely inter-
nal to a given DPG: shared-memory pages, including
across multiple processes; pipes allocated with pipe;
and futexes (used to implement pthreads synchroniza-
tion). Additionally, dOS guarantees that file descriptors
and memory pages are allocated in a deterministic order;
that the address space evolves deterministically (as via
mmap); that internal signals are delivered deterministi-
cally; and that wait is deterministically notified when
threads in the same DPG exit.
Note that some system calls are deterministic except
in error cases. For example, mmap allocates pages deter-
USENIX Association
USENIX Association
ministically within an address space, but will fail non-
deterministically if there is not enough physical memory
available to service the request.
Guarantees Not Provided by dOS Our deterministic
guarantees may not translate across different versions of
program binaries no matter how slightly different (e.g.,
after a patch). Also, although our guarantees hold across
different host machines, an application can read host con-
figuration as part of its inputs, for example to dynami-
cally adjust its resource usage; these inputs must be du-
plicated exactly to guarantee determinism.
Additionally, dOS does not guarantee deterministic
access to shared-memory pages that can be modified by
threads or devices outside the DPG. Ideally we might in-
terpose on this external communication using the shim,
but this would require adding excessive restrictions to
non-DPG processes. For example, page ownership might
transition between “exclusive to a DPG” and “exclusive
to the external world,” but this would require stalling ex-
ternal threads as they wait to reacquire page ownership.
Relatedly, DPGs may encounter nondeterminism when
memory is modified through backdoors in /proc.
Retrospective Implementing DPGs in a monolithic ker-
nel such as Linux raises many thorny issues. The exam-
ple of mmap is instructive: reasoning about the cases in
which mmap is nondeterministic requires finding and rea-
soning about many code paths in a monolithic kernel.
More generally, providing determinism requires track-
ing and mediating accesses to shared OS objects. How-
ever, many Linux kernel objects have aliased names,
are named in multiple namespaces, and are accessible
through multiple interfaces. For example, process IDs are
exposed through system calls, the /proc filesystem in-
terface, and in some cases, thread-local storage variables
in the address space of a multithreaded process. If we
consider PIDs to be a source of internal nondeterminism,
dOS must correctly track and reconcile PIDs through all
of these channels, for instance, by virtualizing PID num-
bers before they are exposed to a program so that PID
assignment is deterministic and consistent across pro-
cesses within a DPG. Even if we consider PIDs a source
external nondeterminism (the choice made by dOS), for
record/replay to work correctly a shim program must in-
terpose on all of these different channels for accessing
PIDs, so that PIDs can be recorded and during replay the
same PIDs can be reassigned.
An OS kernel implemented “from scratch” to support
DPGs would benefit from design principles advocated
by exokernels and microkernels. A minimal kernel in-
terface combined with a libOS would push many of the
aliased interfaces and complex code paths out of the ker-
nel and inside the user-space deterministic box, making
it easier to reason about determinism at the system call
layer. The protection domains of a microkernel could fur-
ther simplify many of these issues, since reasoning about
nondeterminism would largely reduce to detecting mes-
sages that cross the boundary of a deterministic box. In
the mmap example, this might be a message to the page-
allocation server.
5. Shim Applications
To demonstrate the usefulness of the shim layer, we
have implemented three shims: deterministic filesystem
services; record/replay by logging just external input;
and replicated execution of a multithreaded server. The
deterministic filesystem service and record/replay shims
can be used with unmodified application binaries, while
the replicated execution shim is application specific. We
note that the shim layer allowed us to quickly prototype
the shims described in this section.
5.1 Deterministic Filesystem Services
FSSHIM provides applications with a deterministic file
hierarchy. All reads and writes to files within this hierar-
chy are deterministic; accesses to files outside of this hi-
erarchy are considered sources of external nondetermin-
ism, as before. There are two sources of nondeterminism
FSSHIM must eliminate: the latency of each operation,
and the number of bytes operated on by the read and
write system calls. FSSHIM eliminates the first by de-
ciding, a priori, that each operation will block for a fixed
and deterministic amount of logical time. For the second,
FSSHIM guarantees that all reads and writes operate
on a deterministic number of bytes by always performing
the maximum amount of work requested (up to an end-
of-file, for reads). FSSHIM can make these guarantees
because it performs the read and write calls on behalf
of the DPG, using the pattern illustrated in Figure 5.
The deterministic blocking time selected by FSSHIM
can affect performance. For example, if FSSHIM selects
a logical blocking time that is too low, the DPG will stall
waiting for disk operations to complete. On the other
hand, if FSSHIM selects a time that is too high, the call-
ing thread will execute artificially slowly. The logical
blocking times we chose for FSSHIM are equivalent to
a delay of about 5 million instructions; we did not exper-
iment heavily with this number.
A file can exist in the deterministic file hierarchy only
if it can be considered internal to the DPG, which is true
when: (1) the initial contents of the file are deterministic;
(2) the file is not written by any threads outside the DPG;
and (3) operations on that file complete in a finite time. In
practice, the third assumption implies fail-stop. FSSHIM
relies on the user to explicitly indicate the parts of the
filesystem for which these assumptions are valid. This
typically includes the directories containing program in-
puts, as well as directories shared system-wide that are
rarely updated, such as /usr and /etc.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) —:185
186
5.2 Record/Replay
RECSHIM records all external nondeterminism intro-
duced through the system call interface and signals, en-
abling deterministic program replay. RECSHIM needs to
record only the external nondeterminism because DPGs
eliminate all forms of internal nondeterminism. Further,
RECSHIM can be combined with FSSHIM, reducing
what needs to be logged since accesses to files within
the deterministic file hierarchy would be deterministic.
RECSHIM utilizes the shim layer to interpose on sys-
tem calls and to intercept external signals. System calls
that touch user-space memory are executed by RECSHIM
on behalf of the DPG. RECSHIM produces a log con-
taining the logical time the event occurred and any other
event-specific information needed during replay. For sys-
tem calls, this includes the return value and logical block-
ing time, as well as any side-effects of the system call,
such as the contents of a buffer after performing a socket
read. For signals, a copy of the siginfo is saved. Logs
are compressed on-the-fly with zlib.
We have implemented a proof-of-concept replay shim
to verify that the shim layer offers all the hooks necessary
to implement a replay component. The major challenges
in faithfully replaying system call traces are orthogonal
to the main body of our work and have been explored by
prior work [19, 36, 37].
5.3. Replicated Execution
REPLICASHIM supports replication of a multithreaded
webserver running inside a DPG by guaranteeing that
the order of messages and their logical arrival time is
kept consistent across all replicas. Given the same inputs
and the same logical arrival times, the DPG abstraction
guarantees that all replicas will evolve deterministically.
Our target application is nullhttpd [12], a small,
simple, multithreaded webserver that uses a thread-per-
request model. Our design splits the functionality of the
basic server into three separate process types: a single
arbiter process, and a set of replicas, each composed of a
shim process along with a DPG that hosts nullhttpd.
The arbiter process operates nondeterministically,
outside of any DPG, and accepts incoming HTTP re-
quests from the network. The arbiter broadcasts requests
to the replicated shims, which queue the requests locally.
We modified nullhttpd to read new requests by mak-
ing a direct call to its shim via dpg_callshim, rather
than reading from the network. This shows a case where
the programmer defines the interface via which nonde-
terministic inputs are received.
When the arbiter broadcasts a request, it must ensure
that all replicas see that request at the same logical time.
It does this by performing a two-phase commit to deter-
mine a logical time that no replica has advanced beyond.
The protocol works as follows. When the arbiter receives
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
a new HTTP request from the network, it asks all repli-
cated shims to set a barrier and report their current logi-
cal time. The arbiter uses the maximum value reported by
any replica as the logical arrival time of the new request.
The arbiter then broadcasts the new request and asks each
replica’s shim to set a second barrier for this arrival time;
once this barrier is reached at a replica, the replica’s shim
makes the new request available to nullhttpd and the
replicas continue to evolve deterministically.
6. Evaluation
The goal of our evaluation is to understand the perfor-
mance of DPGs in comparison to ordinary nondetermin-
istic execution (Nondet). We include evaluations of the
three shim programs we built, namely FSSHIM, REC-
SHIM, and REPLICASHIM.
Correctness We tested our dOS implementation by run-
ning the racey [20, 45] deterministic stress test 500
times and verifying that racey always produces the same
output. In addition to the basic racey program, we tested
racey variants that exercise the various components of
our implementation, such as communication via pipes,
signals, and multiprocess shared-memory.
Workloads We evaluated the following parallel work-
loads: the PARSEC [8] and SPLASH2 [44] benchmark
suites; pbzip2 [18] to compress a Linux ISO image;
and make -j to perform a parallel build of the Linux
kernel. The PARSEC and SPLASH2 are workloads opti-
mized for parallelism; we scaled their inputs to run for
about a minute with a single nondeterministic thread.
We present a representative subset of the PARSEC and
SPLASH2 benchmarks that was selected to showcase both
the best-case and worst-case performance of dOS.
We also evaluated three reactive applications: the
Apache and nullhttpd webservers and the Chromium
web browser. Apache and Chromium are especially in-
teresting because they use multiple processes with mul-
tiple threads per process. We evaluated the webservers
using httperf [26] to simulate a constant stream of re-
quests for static pages. We evaluated Chromium with
two experiments: first, we measured the load time of
nytimes.com (without any local caching); and second,
we used Chromium’s debugging facilities to execute a
scripted user session that opened 5 tabs and navigated
to 12 URLs in rapid succession. All Chromium experi-
ments used the process-per-tab model [34].
We ran our experiments on 8-core 2.8GHz Intel Xeon
E5462 machines with 10GB of RAM using rundet, a
small utility that constructs a single DPG and then exe-
cutes an unmodified application binary inside that DPG.
We used a relatively aggressive machine configuration to
adequately explore the scalability of our parallel work-
loads. All results shown are the average over ten execu-
tions, with the highest and lowest values removed.
USENIX Association
USENIX Association
Config Throughput
Num Threads DPG DPG+
Benchmark Proc perProc | Nondet only FSSHIM
apache 10KB 16 1 10.1K 3.6K 1.7K req/s
apache 10KB 4 4 10.1K 6.6K 2.2K req/s
apache 10KB 1 16 10.2K 7.4K 2.4K req/s
apache 100KB 4 8 11K 1.1K 0.9K reqs/s
nullhttpd 1OKB 1 16 10K 1.0K _ 1.0K req/s
chromium nytimes 18s 24s 3.85
chromium scripted 22s 37s 40s
Table 2. Reactive Workload Evaluation
Overheads Speedup
(relative to Nondet) (8-th over 2-th)
DPG Only DPG + FsSHIM
Benchmark | 2-th 4-th 8-th | 2-th 4+th 8-th | Nondet FSSHIM
blackscholes | 1.2 1.2 13/12 13 13 3.4 3.2
dedup 23 36 40/40 58 64 1.6 1.0
fmm 26 61 101/26 60 10.1 2.4 0.6
lu 20 23 23/20 2.3 23 2.1 1.7
pbzip2 20 2.7 30/21 28 34 2.6 1.6
make 2.3 41 59 | 32 5.7 82 2.8 11
Table 3. Parallel Workload Evaluation
6.1 DPG Overheads
We start with two questions: what are the overheads of
DPGs for typical workloads, relative to nondeterministic
execution, and how much overhead is added by FSS HIM?
To answer these, we ran our workloads in DPGs with no
shim attached and in DPGs with FSSHIM attached.
Table 2 summarizes this evaluation for reactive work-
loads. The first few rows evaluate Apache for workloads
of 10KB and 100KB static pages. For the 1OOKB work-
load, both the Nondet and the DPG-only case are able
to saturate the gigabit network of the Apache server, in
spite of the extra overhead of using the DPG. FSSHIM
adds some additional overhead, enough to shift the sys-
tem bottleneck to the CPU.
For the 1OKB workload, the Nondet case is still able
to saturate the network link. However, this workload
involves a significantly higher rate of system calls and
other nondeterministic events; each system call incurs a
context switch from the DPG to its shim. As a result,
both the DPG-only and the FSSHIM cases experience
serialization and overhead that slows the request rate
between 1.4x and 5.9x.
Throughput generally decreases as the number of pro-
cesses (Column 2) increases. We suspect this is because
interprocess communication is more costly when exe-
cuting in a DPG. Note that scaling can be achieved by
running multiple smaller instances of Apache in separate
DPGs. Overall, we consider these throughputs reason-
able for all but the most high-traffic web sites.
The last two rows show the execution time of Chro-
mium. For the scripted session, latency increases by 1.7x
for DPGs alone and by 1.8x for DPGs with FSSHIM.
Latency increases from 1.8 seconds to just 2.4 seconds
when loading nytimes.com. We also performed this test
for a Google search results page (not shown). All execu-
Config Exec Breakdown Serialization
Num Num | % Serial % Single Reasons
Benchmark Proc Thread | Mode Stepping | % Pgfault % Syscall
apache 10KB 16 1 12% < 1% < 1% 99%
apache 10KB 4 4 80% < 1% < 1% 99%
apache 10KB 1 16 82% < 1% < 1% 99%
apache 100KB 4 8 26% < 1% 2% 98%
nullhttpd 1OKB | 1 16 11% 0% 2% 98%
chromium nytimes 58% 13% 61% 39%
chromium scripted 25% 13% 72% 28%
blackscholes 1 8 3% 27% 99% 1%
dedup 1 8 54% 12% 771% 23%
fmm 1 8 90% 18% 100% 0%
lu 1 8 45% 35% 95% 5%
pbzip2 1 8 35% 39% 100% 0%
make 8 1 79% 3% 0% 100%
Table 4. DPG Execution Characterization
tion times in the Google search results test were less than
a second, and informally, the differences “felt” negligible
when we interacted with the browser.
Table 3 shows execution overheads for our parallel
workloads with 2, 4, and 8 threads. Overheads are gen-
erally below 3x, often lower than 2.5. Columns 5-7
show the added cost of FSSHIM, which is typically small,
since most of the applications do not perform a signifi-
cant number of system calls (except dedup and make).
DPG scalability is closer to Nondet scalability when
the overheads do not grow much with the number of
threads. Scalability suffers for workloads like fmm that
share frequently at finer than page-level granularity, but
blackscholes, which does not have fine-grained shar-
ing, has DPG scalability very close to Nondet.
Characterization Table 4 characterizes execution with
DPGs with FSSHIM attached. Column 4 shows the frac-
tion of time execution was serialized (i.e., in serial
mode). As expected, for the parallel workloads, serializa-
tion is highly correlated with overheads and scalability.
blackscholes and fmm are good comparison points;
blackscholes is 3% serialized and scales nearly ide-
ally with DPGs, while fmm is 90% serialized and has
poor scalability. For the reactive workloads, the rela-
tionship between serialization and performance is less
clear, as shim context switch overhead and quantum im-
balance are also important factors. The rightmost set of
columns show the reason for serialization, broken down
into ownership page faults (Column 6) and system calls
(Column 7). In reactive workloads, most serialization
happens due to system calls, which is expected because
reactive workloads perform frequent I/O. Conversely,
for parallel workloads (except make), most serialization
is due to ownership page faults. Also, the fact that dOS
uses page-level ownership tracking can lead to unneces-
sary serialization due to false-sharing.
Even though serialization is very low in blackscholes,
the overheads are still on the order of 30%, largely be-
cause of single-stepping. Column 5 shows the fraction
of execution during which at least one thread is single-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) —-187
188
Overheads Log Sizes (per day)
w/ w/o w/ w/o SMP-ReVirt
Benchmark | FSSHIM FSSHIM || FSSHIM FSSHIM (from [16])
fmm 6.0 6.0 1.1 MB 2.0MB 83.6 GB
lu 2.4 2.4 110MB~ 13.0MB_ 11.7GB
ocean 3.0 3.0 1.5 MB 3.6 MB 28.1 GB
radix 4.5 4.5 0.8 MB 2.1MB 88.7 GB
water 48 48 5.3MB- 83.2MB_ 58.5 GB
pbzip2 2.9 4.0 5.7MB 295.7 GB —
Table 5. RECSHIM for Parallel Workloads (4 threads)
stepping; this varies from 0% to 39%. One interesting
trend is that reactive applications single-step less of-
ten; these applications perform system calls frequently,
which triggers an optimization to end quanta early (Sec-
tion 4.2). Note that single-stepping does not necessar-
ily correlate with performance because serialization and
quantum imbalance dominate. In addition to data shown
here, we measured the increase in frequency of total page
fault events due to ownership changes. While the fre-
quency is often higher, it was not directly correlated
with performance. Serialization, quantum imbalance,
and single-stepping are the dominant factors.
In summary, DPG overheads are reasonable for sev-
eral applications, including some parallel applications
and most reactive applications. Broadly, overhead tends
to increase with sharing, especially as the number of
threads grows. We did not attempt to optimize appli-
cations for more “determinism friendly” sharing, which
could improve performance.
Microbenchmark To more closely understand the over-
head of intercepting system calls with a shim, we wrote
a simple benchmark that does nothing but call getpid
in a loop. We ran this benchmark both in a DPG without
a shim, and in a DPG with a “null-shim.” The null-shim
configuration ran 5x slower, suggesting that dOS im-
poses an overhead of 5x on system call entry.
6.2 RECSHIM: Execution Recorder Shim
We next evaluated the overhead of using RECSHIM, and
its resulting log sizes. Table 5 characterizes RECSHIM
for parallel workloads. Columns 2-3 show the overheads
for RECSHIM with and without a deterministic file hier-
archy, respectively. These overheads are essentially iden-
tical to execution without RECSHIM (Table 3). Columns
4-5 show log sizes for a full day of execution. REC-
SHIM’s log sizes are very small because DPGs eliminate
internal nondeterminism; the remaining nondeterminism
is due to a few system calls such as gettimeofday.
Not making filesystem accesses deterministic (Col-
umn 5) increases the sources of nondeterminism, lead-
ing to larger logs. This is especially true for pbzip2,
which must log the entire ISO image. These log sizes,
however, are still orders of magnitude lower than the
sizes reported by SMP-ReVirt [16] (Column 6). This
is because SMP-ReVirt needs to record internal non-
determinism (again, especially shared-memory), which
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
Config Throughput Log Sizes
Num_ Threads w/ w/o w/
Benchmark Proc per Proc |} FSSHIM FSSHIM FSSHIM
apache 1|OKB | 16 1 1.7K req/s 1.6K req/s || 48.6 B/req
apache 1|OKB| 4 4 2.3K req/s 2.1K req/s |} 51.3 B/req
apache 10KB 1 16 2.2K req/s 2.2K req/s || 50.4 B/req
chromium nytimes 4.25 3.9s 600 KB
chromium scripted 40s 43s 3.3 MB
Table 6. RECSHIM for Reactive Workloads
Config Throughput
lreplica 2 replicas
Nondet 386 req/s 373 req/s
REPLICASHIM | 369req/s 372 req/s
Table 7. Replicated Execution Overheads
can be massive. Since SMP-ReVirt is a hypervisor, it
logs nondeterminism internal to the OS, adding overhead
for radix that a process-level implementation of SMP-
ReVirt might be able to avoid. This is also an indication
that determinism enforcement at the hypervisor level is
likely to have a higher performance cost than when en-
forced at the process level.
Table 6 shows overheads and log sizes for RECSHIM
when running reactive applications. Columns 4-5 show
the throughput while recording, both with and without
FSSHIM enabled. With FSSHIM enabled, RECSHIM did
not reduce the throughput of the webservers from the
results shown in Table 2. However, disabling FSSHIM
resulted in a small performance decrease. The decreased
performance is due to the overhead of logging additional
input. The overheads for Chromium are about the same
as those seen in Table 2. Column 6 shows log sizes
normalized to the number of requests for the Apache
runs, as well as total log sizes for Chromium sessions.
6.3. REPLICASHIM: Replicated Execution Shim
We end our evaluation by investigating whether we can
we quickly build on DPGs to enable replication of an ex-
isting multithreaded application. To answer this, we built
and tested REPLICASHIM, which replicates our modified
nullhttpd. For a performance comparison, we also ran
replicas outside DPGs but still using the same arbiter and
replication protocol (Nondet). This configuration does
not provide any deterministic guarantees. Table 7 shows
the throughput for | and 2 replicas with 16 threads per
replica; each replica ran on a separate machine while the
arbiter ran on a third machine. In both cases, the through-
put is essentially matched. Note we did not spend much
time optimizing the arbiter or its simplistic protocol, as
REPLICASHIM is only as a proof-of-concept; the arbiter
is the major bottleneck in these experiments.
6.4 Summary
Our evaluation illuminated the impact of determinism
on application performance and scalability. Workload
is fundamental factor: applications with frequent inter-
USENIX Association
USENIX Association
thread or inter-process sharing will encounter more over-
head and worse scalability when executed deterministi-
cally, since this communication must be tracked and con-
trolled. Implementation choices also have a large impact.
We suspect that much of the overhead in dOS is not fun-
damental and might be mitigated by using sharing-aware
memory allocation, by fine-tuning integration with the
Linux scheduler, or by using potential upcoming hard-
ware support for transactional memory [2].
The choice of deterministic execution algorithm is
another factor. Algorithms like DMP-O that provide a
strict memory model or make heavy use of barriers will
likely perform worse than those that that loosen the mem-
ory model or rely on alternative mechanisms such as
speculation. dOS could have implemented the DMp-TM
and DMP-B algorithms we developed in earlier work [6,
14]. Both algorithms have better demonstrated scalabil-
ity than DMP-O and can both be implemented at the ker-
nel level, but both algorithms are more complex. The key
idea of DMP-B is to relax the memory model by using a
store buffer, which allows concurrent writes in the same
quantum round and therefore improves scalability. The
key idea of DMP-TM is to use transactional memory to
speculate that each quantum round is conflict-free and
thus can be executed in completely parallel.
7. Related Work
Deterministic Execution There are a few recent pro-
posals for removing internal nondeterminism in multi-
threaded execution. DMP [14] is a hardware proposal
that includes two approaches for deterministic execution:
DmpP-O uses ownership tracking at a cache-line granu-
larity; DMP-TM uses transactional memory [33] to fur-
ther reduce the cost of determinism by speculating that
there is no communication between threads. Kendo [29]
proposes a software-only library that provides a set of
deterministic synchronization operations that offer some
deterministic guarantees for race-free programs. Core-
Det [6] proposed DMP-B and used compiler and run-
time system to provide determinism for arbitrary C/C++
programs. Grace [7] uses speculative execution to pro-
vide determinism for fork-join parallel programs. These
proposals all describe algorithms for execution-level de-
terminism, as used by DPGs. Unlike these prior propos-
als, however, DPGs support determinism beyond shared-
memory in arbitrary binary programs and also provide a
way to precisely control external nondeterminism.
Another approach is language-level determinism,
which uses a parallel language that is deterministic
by construction, such as StreamIt [41], SHIM [17],
NESL [10], Jade [35], or DPJ [11]. The prime trade-off
between execution-level and language-level determinism
is one of generality and controllability. In language-level
determinism, the programmer must use specific language
constructs but gets explicit control of which deterministic
executions are possible; in execution-level determinism
the programmer can use any language (i.e., determinism
is fully transparent) but cannot control which determin-
istic executions will happen, making behavior less pre-
dictable at program construction time. While determinis-
tic languages are a promising long-term solution, the ma-
jority of today’s programs are written in mainstream lan-
guages such as C++ or Java, and this will likely remain
the case for the foreseeable future. Additionally, parallel
languages are often domain-specific and not well suited
to general purpose, reactive applications; in contrast, we
have used dOS to demonstrate how reactive applications
can benefit from execution-level determinism.
Determinator [3] proposes to enforce determinism us-
ing a custom microkernel. Like dOS, Determinator sup-
ports multiple processes and uses page protection to en-
force determinism of shared-memory accesses. Determi-
nator supports both standard pthreads programs, via an
implementation of DMP-B, as well as programs written
using specialized parallel programming constructs that
are designed to be deterministic. Unlike dOS, however,
Determinator does not explore the separation between
internal and external nondeterminism, and further, Deter-
minator has no equivalent of the DPG shim layer inter-
face for precisely controlling external nondeterminism.
Record/Replay Record and replay is a natural way to
cope with internal nondeterminism during debugging.
There are many proposals for software-based implemen-
tations of record and replay. Some record all shared
accesses that lead to communication [22]; others as-
sume uniprocessor execution and record only schedul-
ing decisions [13]; others record only synchronization
operations [36]. The high overheads of logging shared-
memory communication motivated several proposals
for hardware-supported recording [24, 45, 46], includ-
ing some recent OS work on virtualization of hardware
mechanisms for recording [25].
More recent work [1, 31, 47] relaxes the guarantees
of replay by recording just a subset of the information
required for faithful deterministic replay. The result is a
smaller log at the cost of requiring a potentially impracti-
cal search of the execution space during replay. ESD [48]
uses symbolic execution to reconstruct thread schedules
given only a core dump, without requiring any execution
logs to begin with. Unfortunately, ESD suffers from the
incompleteness problems faced by symbolic execution,
and thus cannot guarantee that a suitable execution will
be found during replay.
Two recent and notable record/replay systems are
SMP-ReVirt [16] and Scribe [21]. Both systems use page
ownership similarly to dOS but record ownership transi-
tions rather than imposing a single deterministic order,
as in dOS. SMP-ReVirt is a hypervisor, and so it sup-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) —:189
190
ports full-system replay only, while Scribe is a kernel ex-
tension, allowing it to support replay of process groups
much like dOS. Additionally, Scribe and dOS use similar
strategies to track ownership changes of kernel objects.
In contrast to all record/replay systems, the determin-
ism guaranteed by DPGs enables precise replay without
needing to record any internal nondeterminism.
Replicated Execution Most prior work in multithread-
ed replicas has taken the approach of recording and repli-
cating internal nondeterminism. Examples include sys-
tems that assume a uniprocessor [28, 40]; that assume
race-freedom [4, 5]; and that conservatively replicate
all potential shared-memory nondeterminism [39]. Re-
cently, Replicant [32] proposed a limited form of de-
terministic execution specifically for the purpose of de-
terministic replication, but this approach requires pro-
grammer annotations. Most recently, Respec [23] exe-
cutes replicas independently while periodically verifying
consistency; when consistency is violated, replicas are
rolled back to a consistent state and execution proceeds
more conservatively. Respec does not support replication
across more than one machine, limiting its usefulness.
In contrast to prior systems, the determinism offered by
DPGs naturally enables replication.
There are some parallels between how dOS provides
deterministic execution within a process group and how
toolkits like Isis [9] and Horus [42] provide virtually syn-
chronous execution to a distributed process group. Isis
provides totally ordered multicast primitives that guaran-
tee all processes see messages in the same order, a pow-
erful building block for consistent updates of distributed
replicas; dOS implements DMP-O to enforce a determin-
istic order on both implicit shared-memory and explicit
OS-channel communications between threads and pro-
cesses. Unlike dOS, Isis does not guarantee the deter-
ministic execution of a process or the deterministic tim-
ing of message delivery relative to processes’ instruction
sequence. Unlike Isis, dOS does not provide fault tol-
erance, distributed group membership services, or state
transfer to new group members.
8. Conclusions
We introduced the DPG abstraction, which allows pro-
grammers to define a deterministic box inside which all
communication happens deterministically. We described
the shim layer, an interface through which external non-
determinism can be observed and controlled by user-
space programs. We developed dOS, an implementation
of DPGs in Linux. We demonstrated the shim layer with
three applications: record/replay, multithreaded replica-
tion, and deterministic filesystem services.
Our evaluation showed that DPGs have reasonable
cost in reactive applications such as Apache and Chro-
mium, and also in several parallel workloads. This con-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
ceivably enables deterministic execution in deployment,
which would fully leverage the benefits of determinism
in testing, reliability and debugging.
9. Acknowledgments
We thank Karin Strauss, Dan Grossman, John Zahor-
jan, and the members of the UW Sampa and systems re-
search groups for their feedback and help. We also thank
our anonymous reviewers and our shepherd, Ed Nightin-
gale, for their guidance. This work was supported in part
by the National Science Foundation under grants CNS-
0627367, CNS-0430477, and CAREER award 0846004,
a Torode Family Endowed Career Development Profes-
sorship, a Microsoft Faculty Fellowship, and gifts from
Nortel Networks and Intel Corporation.
References
[1] G. Altekar and I. Stoica. ODR: Output-Deterministic
Replay for Multicore Debugging. In SOSP, 2009.
[2] AMD. Advanced Synchronization Facility: Proposed
Architectural Specification. http://developer.amd.
com/cpu/ASF/Pages/default.aspx, March 2009.
[3] A. Aviram, S.-C. Weng, S. Hu, and B. Ford. Efficient
System-Enforced Deterministic Parallelism. In OSDI,
2010.
[4] C. Basile, Z. Kalbarczyk, and R. Iyer. A Preemptive
Deterministic Scheduling Algorithm for Multithreaded
Replicas. In International Symposium on AUtomated
Analysis-driven Debugging, 2005.
[5] C. Basile, Z. Kalbarczyk, and R. Iyer. Active Replication
of Multithreaded Applications. JEEE Trans. Parallel Dis-
trib. Syst., 17(5), 2006.
[6] T. Bergan, O. Anderson, J. Devietti, L. Ceze, and
D. Grossman. CoreDet: A Compiler and Runtime System
for Deterministic Multithreaded Execution. In ASPLOS,
2010.
[7] E. Berger, T. Yang, T. Liu, and G. Novark. Grace: Safe and
Efficient Concurrent Programming. In OOPSLA, 2009.
[8] C. Bienia, S. Kumar, J. P. Singh, and K. Li. The PARSEC
Benchmark Suite: Characterization and Architectural Im-
plications. In PACT, 2008.
[9] K. P. Birman. The Process Group Approach to Reliable
Distributed Computing. Communications of the ACM,
36(12), December 1993.
[10] G. Blelloch. NESL: A Nested Data-Parallel Language
(Version 3.1). Technical report, CMU.
[11] R. Bocchino, V. Adve, D. Dig, S. Adve, S. Heumann,
R. Komuravelli, J. Overbey, P. Simmons, H. Sung, and
M. Vakilian. A Type and Effect System for Deterministic
Parallel Java. In OOPSLA, 2009.
[12] D. Cahill. NullLogic HTTPd. http://www.nulllogic.
ca/httpd/.
USENIX Association
USENIX Association
[13] J. Choi and H. Srinivasan. Deterministic Replay of Java
Multithreaded Applications. In SIGMETRICS SPDT,
1998.
[14] J. Devietti, B. Lucia, L. Ceze, and M. Oskin. DMP: De-
terministic Shared Memory Multiprocessing. In ASPLOS,
2009.
[15] G. Dunlap, S. King, S. Cinar, M. Basrai, and P. Chen.
ReVirt: Enabling Intrusion Analysis Through Virtual-
Machine Logging and Replay. In OSDI, 2002.
[16] G. Dunlap, D. Lucchetti, M. Fetterman, and P. Chen. Exe-
cution replay of multiprocessor virtual machines. In VEE,
2008.
[17] S. A. Edwards and O. Tardieu. SHIM: A Deterministic
Model for Heterogeneous Embedded Systems. In EM-
SOFT, 2005.
[18] J. Gilchrist. pbzip2: parallel bzip2.
compression.ca/pbzip2.
[19] Z. Gu, X. W, J. Tang, X. Liu, Z. Xu, M. Wu, F. Kaashoek,
and Z. Zhang. R2: An Application-Level Kernel for
Record and Replay. In OSDI, 2008.
[20] M. Hill and M. Xu. Racey: A Stress Test for Determinis-
tic Execution. http: //www.cs.wisc.edu/~markhill/
racey.html.
[21] O. Laadan, N. Viennot, and J. Nieh. Transparent,
Lightweight Application Execution Replay on Commod-
ity Multiprocessor Operating Systems. In SIGMETRICS,
2010.
[22] T. J. LeBlanc and J. M. Mellor-Crummey. Debugging Par-
allel Programs with Instant Replay. IEEE Trans. Comput.,
36(4), 1987.
[23] D. Lee, B. Wester, J. Flinn, S. Narayanasamy, and P. Chen.
Respec: Efficient Multiprocessor Replay via Speculation
and External Determinism. In ASPLOS, 2010.
[24] P. Montesinos, L. Ceze, and J. Torrellas. DeLorean:
Recording and Deterministically Replaying Shared-
Memory Multiprocessor Execution Efficiently. In SCA,
2008.
[25] P. Montesinos, M. Hicks, S. T. King, and J. Torrellas.
Capo: A Software-Hardware Interface for Practical De-
terministic Multiprocessor Replay. In ASPLOS, 2009.
[26] D. Mosberger and T. Jin. httperf: A Tool for Measuring
Web Server Performance. Performance Evaluation Re-
view, 26(4), 1998.
[27] M. Musuvathi, S. Qadeer, T. Ball, G. Basler, P. A. Nainar,
and J. Neamtiu. Finding and Reproducing Heisenbugs in
Concurrent Programs. In OSDI, 2008.
[28] P. Narasimhan, L. Moser, and P. Melliar-Smith. Enforc-
ing Determinism for the Consistent Replication of Multi-
threaded CORBA Applications. In Symposium on Reli-
able Distributed Systems, 1999.
[29] M. Olszewski, J. Ansel, and S. Amarasinghe. Kendo:
Efficient Deterministic Multithreading in Software. In
ASPLOS, 2009.
http://
[30] S. Osman, D. Subhraveti, G. Su, and J. Nieh. The De-
sign an Implementation of Zap: A System for Migrating
Computing Environments. In OSDI, 2002.
[31] S. Parka, W. Xiong, Z. Yin, R. Kaushik, K. Lee, S. Lu,
and Y. Zhou. Do You Have to Reproduce the Bug at the
First Replay Attempt? — PRES: Probabilistic Replay with
Execution Sketching on Multiprocessors. In SOSP, 2009.
[32] J. Pool, I. Wong, and D. Lie. Relaxed Determinism: Mak-
ing Redundant Execution on Multiprocessors Practical. In
HotOS, 2007.
[33] R. Rajwar, M. Herlihy, and K. Lai. Virtualizing Transac-
tional Memory. In JSCA, 2005.
[34] C. Reis and S. Gribble. Isolating Web Programs in Mod-
ern Browser Architectures. In EuroSys, 2009.
[35] M. Rinard and M. Lam. The Design, Implementation, and
Evaluation of Jade. ACM TOPLAS, 20(3), 1988.
[36] M. Ronsse and K. D. Bosschere. RecPlay: A Fully In-
tegrated Practical Record/Replay System. ACM TOCS,
17(2), 1999.
[37] Y. Saito. Jockey: A User-Space Library for Record-
Replay Debugging. In International Symposium on AU-
tomated Analysis-driven Debugging, 2005.
[38] F. Schneider. Implementing Fault-Tolerant Services Using
the State Machine Approach: A Tutorial. ACM Comput.
Surv., 22(4), 1990.
[39] J. Slember and P. Narasimhan. Static Analysis Meets Dis-
tributed Fault-Tolerance: Enabling State-Machine Repli-
cation With Nondeterminism. In HotDep, 2006.
[40] J. Slye and E. Elnozahy. Supporting Nondeterministic
Execution in Fault-Tolerant Systems. In FTCS, 1996.
[41] W. Thies, M. Karczmarek, and S. P. Amarasinghe.
Streamlt: A Language for Streaming Applications. In CC,
2002.
[42] R. Van Renesse, K. Birman, and S. Maffeis. Horus: A
Flexible Group Communication System. Communications
of the ACM, 39(4), April 1996.
[43] V. Weaver and S. McKee. Can Hardware Performance
Counters be Trusted? In IJISWC, 2008.
[44] S.C. Woo, M. Ohara, E. Torrie, J. P. Singh, and A. Gupta.
The SPLASH-2 Programs: Characterization and Method-
ological Considerations. In JSCA, 1995.
[45] M. Xu, R. Bodik, and M. Hill. A “Flight Data Recorder”
for Enabling Full-System Multiprocessor Deterministic
Replay. In SCA, 2003.
[46] M. Xu, M. Hill, and R. Bodik. A Regulated Transitive Re-
duction for Longer Memory Race Recording. In ASPLOS,
2006.
[47] D. Yuan, H. Mai, W. Xiong, L. Tan, Y. Zhou, and S. Pa-
supathy. SherLog: Error Diagnosis by Connecting Clues
From Run-time Logs. In ASPLOS, 2010.
[48] C. Zamfir and G. Candea. Execution Synthesis: A Tech-
nique for Automated Software Debugging. In EuroSys,
2010.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI’10) = 191
USENIX Association
Efficient System-Enforced Deterministic Parallelism
Amittai Aviram, Shu-Chun Weng, Sen Hu, Bryan Ford
Yale University
Abstract
Deterministic execution offers many benefits for debug-
ging, fault tolerance, and security. Current methods
of executing parallel programs deterministically, how-
ever, often incur high costs, allow misbehaved software
to defeat repeatability, and transform time-dependent
races into input- or path-dependent races without elim-
inating them. We introduce a new parallel program-
ming model addressing these issues, and use Determina-
tor, a proof-of-concept OS, to demonstrate the model’s
practicality. Determinator’s microkernel API provides
only “shared-nothing” address spaces and determinis-
tic interprocess communication primitives to make ex-
ecution of all unprivileged code—well-behaved or not—
precisely repeatable. Atop this microkernel, Determi-
nator’s user-level runtime adapts optimistic replication
techniques to offer a private workspace model for both
thread-level and process-level parallel programing. This
model avoids the introduction of read/write data races,
and converts write/write races into reliably-detected con-
flicts. Coarse-grained parallel benchmarks perform and
scale comparably to nondeterministic systems, on both
multicore PCs and across nodes in a distributed cluster.
1 Introduction
We often wish to run software deterministically, so that
from a given input it always produces the same out-
put. Determinism is the foundation of replay debug-
ging [37, 39, 46, 56], fault tolerance [15, 18,50], and ac-
countability mechanisms [30, 31]. Methods of intrusion
analysis [22, 34] and timing channel control [4] further
assume the system can enforce determinism even on ma-
licious code designed to evade analysis. Executing par-
allel software deterministically is challenging, however,
because threads sharing an address space—or processes
sharing resources such as file systems—are prone to non-
deterministic, timing-dependent races [3, 40, 42,43].
User-space techniques for parallel deterministic exe-
cution [8, 10, 20, 21,44] show promise but have limi-
tations. First, by relying on a deterministic scheduler
residing in the application process, they permit buggy
or malicious applications to compromise determinism
by interfering with the scheduler. Second, determinis-
tic schedulers emulate conventional APIs by synthesiz-
ing a repeatable—but arbitrary—schedule of inter-thread
interactions, often using an instruction counter as an arti-
ficial time metric. Data races remain, therefore, but their
manifestation depends subtly on inputs and code path
lengths instead of on “real” time. Third, the user-level
instrumentation required to isolate and schedule threads’
memory accesses can incur considerable overhead, even
on coarse-grained code that synchronizes rarely.
To meet the software development, debugging, and
security challenges that ubiquitous parallelism presents,
it may be insufficient to shoehorn the standard nonde-
terministic programming model into a synthetic execu-
tion schedule. Instead we propose to rethink the basic
model itself. We would like a parallel environment that:
(a) is “deterministic by default” [12,40], except when
we inject nondeterminism explicitly via external inputs;
(b) introduces no data races, either at the memory ac-
cess level [25,43] or at higher semantic levels [3]; (c)
can enforce determinism on arbitrary, compromised or
malicious code for security reasons; and (d) is efficient
enough to use for “normal-case” execution of deployed
code, not just for instrumentation during development.
As a step toward such a model, we present Determi-
nator, a proof-of-concept OS designed around the above
goals. Due to its OS-level approach, Determinator sup-
ports existing languages, can enforce deterministic exe-
cution not only on a single process but on groups of in-
teracting processes, and can prevent malicious user-level
code from subverting the kernel’s guarantee of determin-
ism. In order to explore the design space freely, Determi-
nator takes a “clean-slate” approach, making few com-
promises for backward compatibility with existing ker-
nels or APIs. Determinator’s programming model could
be implemented in a legacy kernel for backward compat-
ibility, however, as part of a “deterministic sandbox” for
example [9]. Determinator’s user-level runtime also pro-
vides limited emulation of the Unix process, thread, and
file APIs, to simplify application porting.
Determinator’s kernel enforces determinism by deny-
ing user code direct access to hardware resources whose
use can yield nondeterministic behavior, including real-
time clocks, cycle counters, and writable shared memory.
Determinator constrains user code to run within a hierar-
chy of single-threaded, process-like spaces, each having
a private virtual address space. The kernel’s low-level
API provides only three system calls, with which a space
can synchronize and communicate with its immediate
parent and children. Potentially useful sources of non-
determinism, such as timers, Determinator encapsulates
into I/O devices, which unprivileged spaces can access
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) —-:193
194
only via explicit communication with more privileged
spaces. A supervisory space can thus mediate all non-
deterministic inputs affecting a subtree of unprivileged
spaces, logging true nondeterministic events for future
replay or synthesizing artificial events, for example.
Atop this minimal kernel API, Determinator’s user-
level runtime emulates familiar shared-resource pro-
gramming abstractions. The runtime employs file repli-
cation and versioning [47] to offer applications a logi-
cally shared file system accessed via the Unix file API,
and adapts distributed shared memory [2, 17] to emulate
shared memory for multithreaded applications. Since
this emulation is implemented in user space, applications
can freely customize it, and runtime bugs cannot com-
promise the kernel’s guarantee of determinism.
Rather than strictly emulating a conventional, nonde-
terministic API and consistency model like determinis-
tic schedulers do [8—-10, 21, 44], Determinator explores
a novel private workspace model. In this model, each
thread keeps a private virtual replica of all shared mem-
ory and file system state; normal reads and writes access
and modify this working copy. Threads reconcile their
changes only at program-defined synchronization points,
much as developers use version control systems. This
model eliminates read/write data races, because reads see
only causally prior writes in the explicit synchronization
graph, and write/write races become conflicts, which the
runtime reliably detects and reports independently of any
(real or synthetic) execution schedule.
Experiments with common parallel benchmarks sug-
gest that Determinator can run coarse-grained paral-
lel applications deterministically with both performance
and scalability comparable to nondeterministic environ-
ments. Determinism incurs a high cost on fine-grained
parallel applications, however, due to Determinator’s use
of virtual memory to isolate threads. For “embarrass-
ingly parallel” applications requiring little inter-thread
communication, Determinator can distribute the com-
putation across nodes in a cluster mostly transparently
to the application, maintaining usable performance and
scalability. As a proof-of-concept, however, the cur-
rent prototype has many limitations, such as a restric-
tive space hierarchy, limited file system size, no persis-
tent storage, and inefficient cross-node communication.
This paper makes four main contributions. — First,
we present the first OS designed from the ground
up to offer system-enforced deterministic execution,
for both multithreaded processes and groups of in-
teracting processes. Second, we introduce a private
workspace model for deterministic parallel program-
ming, which eliminates read/write data races and con-
verts schedule-dependent write/write races into reliably-
detected, schedule-independent conflicts. Third, we use
this model to emulate shared memory and file system ab-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
stractions in Determinator’s user-space runtime. Fourth,
we demonstrate experimentally that this model is practi-
cal and efficient enough for “normal-case’” use, at least
for coarse-grained parallel applications.
Section 2 outlines the deterministic programming
model we seek to create. Section 3 then describes the
Determinator kernel’s design and API, and Section 4 de-
tails its user-space application runtime. Section 5 exam-
ines our prototype implementation, and Section 6 evalu-
ates it informally and experimentally. Finally, Section 7
outlines related work, and Section 8 concludes.
2 A Deterministic Programming Model
Determinator’s basic goal is to offer a programming
model that is naturally and pervasively deterministic. To
be naturally deterministic, the model’s basic abstractions
should avoid introducing data races or other nondeter-
ministic behavior in the first place, and not merely pro-
vide ways to control, detect, or reproduce races. To be
pervasively deterministic, the model should behave de-
terministically at all levels of abstraction: e.g., for shared
memory access, inter-thread synchronization, file system
access, inter-process communication, external device or
network access, and thread/process scheduling.
Intermediate design points are possible and may yield
useful tradeoffs. Enforcing determinism only on syn-
chronization and not on low-level memory access might
improve efficiency, for example, as in Kendo [44]. For
now, however, we explore whether a “purist” approach
to pervasive determinism is feasible and practical.
To achieve this goal, we must address timing depen-
dencies in at least four aspects of current systems: in
way applications obtain semantically-relevant nondeter-
ministic inputs they require for operation; in shared state
such as memory and file systems; in the synchroniza-
tion APIs threads and processes use to coordinate; and
in the namespaces with which applications use and man-
age system resources. We make no claim that these are
the only areas in which current operating systems intro-
duce nondeterminism, but they are the aspects we found
essential to address in order to build a working, perva-
sively deterministic OS. We discuss each area in turn.
2.1. Explicit Nondeterministic Inputs
Many applications use nondeterministic inputs, such as
incoming messages for a web server, timers for an in-
teractive or real-time application, and random numbers
for a cryptographic algorithm. We seek not to eliminate
application-relevant nondeterministic inputs, but to make
such inputs explicit and controllable.
Mechanisms for parallel debugging [39, 46, 56], fault
tolerance [15, 18,50], accountability [30,31], and intru-
sion analysis [22, 34] all rely on the ability to replay a
computation instruction-for-instruction, in order to repli-
USENIX Association
USENIX Association
cate, verify, or analyze a program’s execution history.
Replay can be efficient when only I/O need be logged,
as for a uniprocessor virtual machine [22], but becomes
much more costly if internal sources of nondeterminism
due to parallelism must also be replayed [19, 23].
Determinator therefore transforms useful sources of
nondeterminism into explicit I/O, which applications
may obtain via controllable channels, and eliminates
only internal nondeterminism resulting from parallelism.
If an application calls gett imeofday (), for example,
then a supervising process can intercept this I/O to log,
replay, or synthesize these explicit time inputs.
2.2 A Race-Free Model for Shared State
Conventional systems give threads direct, concurrent ac-
cess to many forms of shared state, such as shared mem-
ory and file systems, yielding data races and heisenbugs
if the threads fail to synchronize properly [25, 40, 43].
While replay debuggers [37, 39,46, 56] and deterministic
schedulers [8, 10,20,21,44] make data races reproducible
once they manifest, they do not change the inherently
race-prone model in which developers write applications.
Determinator replaces the standard concurrent access
model with a private workspace model, in which data
races do not arise in the first place. This model gives
each thread a complete, private virtual replica of all log-
ically shared state a thread may access, including shared
memory and file system state. A thread’s normal reads
and writes affect only its private working state, and do
not interact directly with other threads. Instead, Deter-
minator accumulates each threads’s changes to shared
state, then reconciles these changes among threads only
at program-defined synchronization points. This model
is related to and inspired by early parallel Fortran sys-
tems [7,51], replicated file systems [47], transactional
memory [33,52] and operating systems [48], and dis-
tributed version control systems [29], but to our knowl-
edge Determinator is the first OS to introduce a model
for pervasive thread- and process-level determinism.
If one thread executes the assignment “x = y’ while
another concurrently executes ‘y = x’, for example,
these assignments race in the conventional model, but are
race-free under Determinator and always swap z with y.
Each thread’s read of x or y always sees the “old” version
of that variable, saved in the thread’s private workspace
at the last explicit synchronization point.
Figure | illustrates a more realistic example of a game
or simulator, which uses an array of “actors” (players,
particles, etc.) to represent some logical “universe,” and
updates all of the actors in parallel at each time step. To
update the actors, the main thread forks a child thread to
process each actor, then synchronizes by joining all these
child threads. The child thread code to update each ac-
tor is shown “inline” within the main() function, which
struct actor_state actor[nactors];
main()
initialize all elements of actor[] array
for (time = 0; ; time++)
for (i = 0; i < nactors; i++)
if (thread_fork(i) == IN-CHILD)
// child thread to process actor[i]
examine state of nearby actors
update state of actor[i] accordingly
thread _exit();
for (i = 0; i < nactors; i++)
thread_join(:);
Figure 1: C pseudocode for lock-step time simulation,
which contains a data race in standard concurrency mod-
els but is bug-free under Determinator.
under Unix works only with process-level fork () ; De-
terminator offers this convenience for shared memory
threads as well, as discussed later in Section 4.4.
In this example, each child thread reads the “prior”
state of any or all actors in the array, then updates the
state of its assigned actor “in-place,” without any explicit
copying or additional synchronization. With standard
threads this code has a read/write race: each child thread
may see an arbitrary mix of “old” and “new” states as
it examines other actors in the array. Under Determi-
nator, however, this code is correct and race-free. Each
child thread reads only its private working copy of the
actors array, which is untouched (except by the child
thread itself) since the main thread forked that child. As
the main thread rejoins all its child threads, Determina-
tor merges each child’s actor array updates back into the
main thread’s working copy, for use in the next time step.
While read/write races disappear in Determinator’s
model, traditional write/write races become conflicts. If
two child threads concurrently write to the same actor
array element, for example, Determinator detects this
conflict and signals a runtime exception when the main
thread attempts to join the second conflicting child. In
the conventional model, by contrast, the threads’ execu-
tion schedules might cause either of the two writes to
“win” and silently propagate its likely erroneous value
throughout the computation. Running this code under
a conventional deterministic scheduler causes the “win-
ner’ to be decided based on a synthetic, reproducible
time metric (e.g., instruction count) rather than real time,
but the race remains and may still manifest or vanish due
to slight changes in inputs or instruction path lengths.
2.3 A Race-Free Synchronization API
Conventional threads can still behave nondeterministi-
cally even in a correctly locked program with no low-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) —-195
196
level data races. Two threads might acquire a lock in any
order, for example, leading to high-level data races [3].
This source of nondeterminism is inherent in the lock ab-
straction: we can record and replay or synthesize a lock
acquisition schedule [44], but such a schedule is still ar-
bitrary and effectively unpredictable to the developer.
Fortunately, many other synchronization abstractions
are naturally deterministic, such as fork/join, barriers,
and futures [32]. Deterministic abstractions have the key
property that when threads synchronize, program logic
alone determines at what points in the threads’ execu-
tion paths the synchronization occurs, and which threads
are involved. In fork/join synchronization, for exam-
ple, the parent’s thread_join(t) operation and the child’s
thread_exit() determine the respective synchronization
points, and the parent indicates explicitly the thread ¢ to
join. Locks fail this test because one thread’s unlock()
passes the lock to an arbitrary successor thread’s lock().
Queue abstractions such as semaphores and pipes are de-
terministic if only one thread can access each end of the
queue [24, 36], but nondeterministic if several threads
can race to insert or remove elements at either end. A
related draft elaborates on these considerations [5].
Since the multicore revolution is young and most ap-
plication code is yet to be parallelized, we may still have
a choice of what synchronization abstractions to use.
Determinator therefore supports only race-free synchro-
nization primitives natively, although it can emulate non-
deterministic primitives via deterministic scheduling for
compatibility, as described later in Section 4.5.
2.4 Race-Free System Namespaces
Current operating system APIs often introduce nondeter-
minism unintentionally by exposing shared namespaces
implicitly synchronized by locks. Execution timing af-
fects the pointers returned by malloc() or mmap()
or the file numbers returned by open() in multi-
threaded Unix processes, and the process IDs returned
by fork () or the file names returned by mkt emp () in
single-threaded processes. Even if only one thread actu-
ally uses a given memory block, file, process ID, or tem-
porary file, the assignment of these names from a shared
namespace is inherently nondeterministic.
Determinator’s API therefore avoids creating shared
namespaces with system-chosen names, instead favor-
ing thread-private namespaces with application-chosen
names. Application code, not the system, decides where
to allocate memory and what process IDs to assign chil-
dren. This principle ensures that naming a resource re-
veals no shared state information other than what the ap-
plication itself provided. Since implicitly shared names-
paces often cause multiprocessor contention, designing
system APIs to avoid this implicit sharing may be syner-
gistic with recent multicore scalability work [14].
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
Grandchild Space Grandchild Space
“S gynchronization
Child Space Child Space
N Synchronization
~ Root Space .. Working Memory
La... Snapshot
Device yor
Registers
Determinator Kernel
Figure 2: The kernel’s hierarchy of spaces, each contain-
ing private register and virtual memory state.
3 The Determinator Kernel
Having outlined the principles underlying Determina-
tor’s programming model, we now describe its kernel
design. Normal applications do not use the kernel API
directly, but rather the higher-level abstractions provided
by the user-level runtime, described in the next section.
We make no claim that our kernel design or API is the
“right” design for a determinism-enforcing kernel, but
merely that it illustrates one way to implement a perva-
sively deterministic application environment.
3.1 Spaces
Determinator executes application code within an arbi-
trarily deep hierarchy of spaces, illustrated in Figure 2.
Each space consists of CPU register state for a single
control flow, and private virtual memory containing code
and data directly accessible within that space. A De-
terminator space is analogous to a single-threaded Unix
process, with important differences; we use the term
“space” to highlight these differences and avoid confu-
sion with the “process” and “thread” abstractions Deter-
minator emulates at user level, described in Section 4.
As in a nested process model [27], a Determinator
space cannot outlive its parent, and a space can directly
interact only with its immediate parent and children via
three system calls described below. The kernel provides
no file systems, writable shared memory, or other ab-
stractions that imply globally shared state.
Only the distinguished root space has direct access to
nondeterministic inputs via I/O devices, such as console
input or clocks. Other spaces can access I/O devices only
indirectly via parent/child interactions, or via I/O privi-
leges delegated by the root space. A parent space can
thus control all nondeterministic inputs into any unpriv-
ileged space subtree, e.g., logging inputs for future re-
play. This space hierarchy also creates a performance
bottleneck for I/O-bound applications, a limitation of the
current design we intend to address in future work.
USENIX Association
USENIX Association
Call | Interacts with | Description
Put Child space
Get Child space
Ret | Parent space
Copy register state and/or virtual memory range into child, and optionally start child executing.
Copy register state, virtual memory range, and/or changes since the last snapshot out of a child.
Stop and wait for parent to issue a Get or Put. Processor traps also cause implicit Ret.
Table 1: System calls comprising Determinator’s kernel API.
Put | Get | Option | Description
v v Regs | PUT/GET child’s register state.
v v Copy | Copy memory to/from child.
v v Zero | Zero-fill virtual memory range.
v Snap Snapshot child’s virtual memory.
v Start Start child space executing.
v Merge | Merge child’s changes into parent.
v v Perm | Set memory access permissions.
v v Tree Copy (grand)child subtree.
Table 2: Options/arguments to the Put and Get calls.
3.2 System Call API
Determinator spaces interact only as a result of proces-
sor traps and the kernel’s three system calls—Put, Get,
and Ret, summarized in Table 1. Put and Get take sev-
eral optional arguments, summarized in Table 2. Most
options can be combined: e.g., in one Put call a space
can initialize a child’s registers, copy a range of the par-
ent’s virtual memory into the child, set page permissions
on the destination range, save a complete snapshot of the
child’s address space, and start the child executing.
Each space has a private namespace of child spaces,
which user-level code manages. A space specifies a
child number to Get or Put, and the kernel creates that
child if it doesn’t already exist, before performing the re-
quested operations. If the specified child did exist and
was still executing at the time of the Put/Get call, the
kernel blocks the parent’s execution until the child stops
due to a Ret system call or a processor trap. These “ren-
dezvous” semantics ensure that spaces synchronize only
at well-defined points in both spaces’ execution.
The Copy option logically copies a range of virtual
memory between the invoking space and the specified
child. The kernel uses copy-on-write to optimize large
copies and avoid physically copying read-only pages.
Merge is available only on Get calls. A Merge is like a
Copy, except the kernel copies only bytes that differ be-
tween the child’s current and reference snapshots into the
parent space, leaving other bytes in the parent untouched.
The kernel also detects conflicts: if a byte changed in
both the child’s and parent’s spaces since the snapshot,
the kernel generates an exception, treating a conflict as
a programming error like an illegal memory access or
divide-by-zero. Determinator’s user-level runtime uses
Merge to give multithreaded processes the illusion of
shared memory, as described later in Section 4.4. In prin-
ciple, user-level code could implement Merge itself, but
Child (0,0) fj
Child (0,1) fj Child ~ 5 =
——a
Cross-Node
} (rome) Space Migration
t
Determinator Kernel feo
Determinator Kernel
Cluster Node 0 Cluster Node 1
Figure 3: A spaces migrating among two nodes and start-
ing child spaces on each node.
the kernel’s direct access to page tables makes it easy for
the kernel to implement Merge efficiently.
Finally, the Ret system call stops the calling space, re-
turning control to the space’s parent. Exceptions such as
divide-by-zero also cause a Ret, providing the parent a
status code indicating why the child stopped.
To facilitate debugging and prevent untrusted children
from looping forever, a parent can start a child with an
instruction limit, forcing control back to the parent af-
ter the child and its descendants collectively execute this
many instructions. Counting instructions instead of “real
time” preserves determinism, while enabling spaces to
“quantize” a child’s execution to implement scheduling
schemes deterministically at user level [8,21].
Barring kernel or processor bugs, unprivileged spaces
constrained to use the above kernel API alone cannot
behave nondeterministically even by deliberate design.
While a formal proof is out of scope, one straightforward
argument is that the above Get/Put/Ret primitives reduce
to blocking, one-to-one message channels, making the
space hierarchy a deterministic Kahn network [36].
3.3. Distribution via Space Migration
The kernel allows space hierarchies to span not only
multiple CPUs in a multiprocessor/multicore system, but
also multiple nodes in a homogeneous cluster, mostly
transparently to application code. While distribution is
semantically transparent to applications, an application
may have to be designed with distribution in mind to per-
form well. As with other aspects of the kernel’s design,
we make no pretense that this is the “right” approach to
cross-node distribution, but merely one way to extend a
deterministic execution model across a cluster.
Distribution adds no new system calls or options to
the API above. Instead, the Determinator kernel inter-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) —-197
198
prets the higher-order bits in each process’s child num-
ber namespace as a “node number’ field. When a space
invokes Put or Get, the kernel first logically migrates the
calling space’s state and control flow to the node whose
number the user specifies as part of its child number
argument, before creating and/or interacting with some
child on that node, as specified in the remaining child
number bits. Figure 3 illustrates a space migrating be-
tween two nodes and managing child spaces on each.
Once created, a space has a home node, to which the
space migrates when interacting with its parent on a Ret
or trap. Nodes are numbered so that “node zero” in
any space’s child namespace always refers to the space’s
home node. If a space uses only the low bits in its
child numbers and leaves the node number field zero, the
space’s children all have the same home as the parent.
When the kernel migrates a space, it first transfers to
the receiving kernel only the space’s register state and
address space summary information. Next, the receiving
kernel requests the space’s memory pages on demand as
the space accesses them on the new node. Each node’s
kernel avoids redundant cross-node page copying in the
common case when a space repeatedly migrates among
several nodes—e.g., when a space starts children on each
of several nodes, then returns later to collect their results.
For pages that the migrating space only reads and never
writes, such as program code, each kernel reuses cached
copies of these pages whenever the space returns to that
node. The kernel currently performs no prefetching or
other adaptive optimizations. Its rudimentary messaging
protocol runs directly atop Ethernet, and does not support
TCP/IP for Internet-wide distribution.
4 Emulating High-Level Abstractions
Determinator’s kernel API eliminates many convenient
and familiar abstractions; can we reproduce them un-
der strict determinism? We find that many familiar ab-
stractions remain feasible, though with important trade-
offs. This section describes how Determinator’s user-
level runtime infrastructure emulates traditional Unix
processes, file systems, threads, and synchronization.
4.1 Processes and fork/exec/wait
We make no attempt to replicate Unix process se-
mantics exactly, but would like to emulate traditional
fork/exec/wait APIs enough to support common
uses in scriptable shells, build tools, and multi-process
“batch processing” applications such as compilers.
Fork: Implementing a basic Unix fork() requires
only one Put system call, to copy the parent’s entire
memory state into a child space, set up the child’s regis-
ters, and start the child. The difficulty arises from Unix’s
global process ID (PID) namespace, a source of nonde-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
terminism as discussed in Section 2.4. Since most ap-
plications use PIDs returned by fork() merely as an
opaque argument to a subsequent waitpid (), our run-
time makes PIDs local to each process: one process’s
PIDs are unrelated to, and may numerically conflict with,
PIDs in other processes. This change breaks Unix appli-
cations that pass PIDs among processes, and means that
commands like ‘ps’ must be built into shells for the same
reason that ‘cd’ already is. This simple approach works
for compute-oriented applications following the typical
fork/wait pattern, however.
Since fork () returns a PID chosen by the system,
while our kernel API requires user code to manage child
numbers, our user-level runtime maintains a “free list” of
child spaces and reserves one during each fork (). To
emulate Unix process semantics more closely, a central
space such as the root space could manage a global PID
namespace, at the cost of requiring inter-space commu-
nication during operations such as fork ().
Exec: A user-level implementation of Unix exec ()
must construct the new program’s memory image, in-
tended to replace the old program, while still execut-
ing the old program’s runtime library code. Our run-
time loads the new program into a “reserved” child space
never used by fork(), then calls Get to copy that
child’s entire memory atop that of the (running) parent:
this Get thus “returns” into the new program. To ensure
that the instruction address following the old program’s
Get is a valid place to start the new program, the run-
time places this Get in a small “trampoline” code frag-
ment mapped at the same location in the old and new
programs. The runtime also carries over some Unix pro-
cess state, such as the the PID namespace and file system
state described later, from the old to the new program.
Wait: When an application calls waitpid () to wait
for a specific child, the runtime calls Get to synchronize
with the child’s Ret and obtain the child’s exit status. The
child may return to the parent before terminating, in or-
der to make I/O requests as described below; in this case,
the parent’s runtime services the I/O request and resumes
the waitpid() transparently to the application.
Unix’s wait () is more challenging, as it waits for
any (i.e., “the first”) child to terminate, violating the
constraints of deterministic synchronization discussed in
Section 2.3. Our kernel’s API provides no system call to
“wait for any child,” and can’t (for unprivileged spaces)
without compromising determinism. Instead, our run-
time waits for the child that was forked earliest whose
status was not yet collected.
This behavior does not affect applications that fork one
or more children and then wait for all of them to com-
plete, but affects two common uses of wait (). First,
interactive Unix shells use wait () to report when back-
USENIX Association
USENIX Association
(a) ‘make -j' (b) ‘make -j'
on Unix on Determinator
CPU 1 Task 1 Task 1
CPU2| Task 2 Task 3 Task 2 Task 3
Time all wait()s return Time all wait()s return
(c) 'make -j2' on Unix (d) ‘make -j2' on Determinator
(nondeterministic) (deterministic)
CPU 1 Task 1 Task 1
CPU2]| Task 2 Task 3 Task 2 Task 3
Time Wait() returns Time wait() returns
Figure 4: Example parallel make scheduling scenarios
under Unix versus Determinator: (a) and (b) with unlim-
ited parallelism (no user-level scheduling); (c) and (d)
with a “2-worker” quota imposed at user level.
ground processes complete; thus, an interactive shell run-
ning under Determinator requires special “nondetermin-
istic I/O privileges” to provide this functionality (and re-
lated functions such as interactive job control). Second,
our runtime’s behavior may adversely affect the perfor-
mance of programs that use wait () to implement dy-
namic scheduling or load balancing in user space.
Consider a parallel make run with or without limiting
the number of concurrent children. A plain ‘make -j’,
allowing unlimited children, leaves scheduling decisions
to the system. Under Unix or Determinator, the kernel’s
scheduler dynamically assigns tasks to available CPUs,
as illustrated in Figure 4 (a) and (b). If the user runs
‘make -—j2’, however, then make initially starts only
tasks 1 and 2, then waits for one of them to complete be-
fore starting task 3. Under Unix, wait () returns when
the short task 2 completes, enabling make to start task 3
immediately as in (c). On Determinator, however, the
wait () returns only when (deterministically chosen)
task 1 completes, resulting in a non-optimal schedule (d):
determinism prevents the runtime from learning which of
tasks 1 and 2 completed first. The unavailability of tim-
ing information with which to make good application-
level scheduling decisions thus suggests a practice of
leaving scheduling to the system in a deterministic en-
vironment (e.g., ‘make -j’ instead of ‘-72’).
4.2 A Shared File System
Unix’s globally shared file system provides a convenient
namespace and repository for staging program inputs,
storing outputs, and holding intermediate results such as
temporary files. Since our kernel permits no physical
state sharing, user-level code must emulate shared state
abstractions. Determinator’s “shared-nothing” space hi-
erarchy is similar to a distributed system consisting only
of uniprocessor machines, so our user-level runtime bor-
rows distributed file system principles to offer applica-
tions a shared file system abstraction.
Child
Process Process
File System
Synchronization
Root File |
Process | System |
Job Inputs | Job Outputs
Determinator Kernel
Figure 5: Each user-level runtime maintains a private
replica of a logically shared file system, using file ver-
sioning to reconcile replicas at synchronization points.
Since our current focus is on emulating familiar ab-
stractions and not on developing storage systems, Deter-
minator’s file system currently provides no persistence:
it effectively serves only as a temporary file system.
While many distributed file system designs may be ap-
plicable, our runtime uses replication with weak consis-
tency [53,55]. Our runtime maintains a complete file
system replica in the address space of each process it
manages, as shown in Figure 5. When a process cre-
ates a child via fork (), the child inherits a copy of
the parent’s file system in addition to the parent’s open
file descriptors. Individual open/close/read/write
operations in a process use only that process’s file sys-
tem replica, so different processes’ replicas may diverge
as they modify files concurrently. When a child termi-
nates and its parent collects its state via wait (), the
parent’s runtime copies the child’s file system image into
a scratch area in the parent space and uses file version-
ing [47] to propagate the child’s changes into the parent.
If a shell or parallel make forks several compiler pro-
cesses in parallel, for example, each child writes its out-
put .o file to its own file system replica, then the par-
ent’s runtime merges the resulting .o files into the par-
ent’s file system as the parent collects each child’s exit
status. This copying and reconciliation is not as ineffi-
cient as it may appear, due to the kernel’s copy-on-write
optimizations. Replicating a file system image among
many spaces copies no physical pages until user-level
code modifies them, so all processes’ copies of identical
files consume only one set of pages.
As in any weakly-consistent file system, processes
may cause conflicts if they perform unsynchronized, con-
current writes to the same file. When our runtime detects
a conflict, it simply discards one copy and sets a con-
flict flag on the file; subsequent attempts to open() the
file result in errors. This behavior is intended for batch
compute applications for which conflicts indicate an ap-
plication or build system bug, whose appropriate solu-
tion is to fix the bug and re-run the job. Interactive use
would demand a conflict handling policy that avoids los-
ing data. The user-level runtime could alternatively use
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) = 199
200
pessimistic locking to implement stronger consistency
and avoid unsynchronized concurrent writes, at the cost
of more inter-space communication.
The current design’s placement of each process’s file
system replica in the process’s own address space has
two drawbacks. First, it limits total file system size to
less than the size of an address space; this is a serious
limitation in our 32-bit prototype, though it may be less
of an issue on a 64-bit architecture. Second, wild pointer
writes in a buggy process may corrupt the file system
more easily than in Unix, where a buggy process must
actually call write() to corrupt a file. The runtime
could address the second issue by write-protecting the
file system area between calls to write (), or it could
address both issues by storing file system data in child
spaces not used for executing child processes.
4.3 Input/Output and Logging
Since unprivileged spaces can access external I/O de-
vices only indirectly via parent/child interaction within
the space hierarchy, our user-level runtime treats I/O as
a special case of file system synchronization. In addition
to regular files, a process’s file system image can contain
special I/O files, such as a console input file and a console
output file. Unlike Unix device special files, Determina-
tor’s I/O files actually hold data in the process’s file sys-
tem image: for example, a process’s console input file
accumulates all the characters the process has received
from the console, and its console output file contains all
the characters it has written to the console. In the current
prototype this means that console or log files can even-
tually “fill up” and become unusable, though a suitable
garbage-collection mechanism could address this flaw.
When a process does a read() from the console,
the C library first returns unread data already in the pro-
cess’s local console input file. When no more data is
available, instead of returning an end-of-file condition,
the process calls Ret to synchronize with its parent and
wait for more console input (or in principle any other
form of new input) to become available. When the par-
ent does a wait () or otherwise synchronizes with the
child, it propagates any new input it already has to the
child. When the parent has no new input for any waiting
children, it forwards all their input requests to its parent,
and ultimately to the kernel via the root process.
When a process does a console write (), the run-
time appends the new data to its internal console output
file as it would append to a regular file. The next time the
process synchronizes with its parent, file system recon-
ciliation propagates these writes toward the root process,
which forwards them to the kernel’s I/O devices. A pro-
cess can request immediate synchronization and output
propagation by explicitly calling fsync().
The reconciliation mechanism handles “append-only”
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
Single-threaded cs
Process |
| Child
| Thread |;
Space
Memory
Reconciliation
File System
Reconciliation
| Multithreaded
| Process
Single-threaded ||
Process
‘Determinator Kernel
Figure 6: A multithreaded process built from one space
per thread, with a master space managing synchroniza-
tion and memory reconciliation.
writes differently from other file changes, enabling con-
current writes to console or log files without conflict.
During reconciliation, if both the parent and child have
made append-only writes to the same file, reconciliation
appends the child’s latest writes to the parent’s copy of
the file, and vice versa. Each process’s replica thus ac-
cumulates all processes’ concurrent writes, though dif-
ferent processes may observe these writes in a different
order. Unlike Unix, rerunning a parallel computation
from the same inputs with and without output redirection
yields byte-for-byte identical console and log file output.
4.4 Shared Memory Multithreading
Shared memory multithreading is popular despite the
nondeterminism it introduces into processes, in part be-
cause parallel code need not pack and unpack messages:
threads simply compute “in-place” on shared variables
and structures. Since Determinator gives user spaces no
physically shared memory other than read-only sharing
via copy-on-write, emulating shared memory involves
distributed shared memory (DSM) techniques. Adapting
the private workspace model discussed in Section 2.2 to
thread-level shared memory involves reusing ideas ex-
plored in early parallel Fortran machines [7,51] and in
release-consistent DSM systems [2, 17], although none
of this prior work attempted to provide determinism.
Our runtime uses the kernel’s Snap and Merge opera-
tions (Section 3.2) to emulate shared memory in the pri-
vate workspace model, using fork/join synchronization.
To fork a child, the parent thread calls Put with the Copy,
Snap, Regs, and Start options to copy the shared part of
its memory into a child space, save a snapshot of that
memory state in the child, and start the child running, as
illustrated in Figure 6. The master thread may fork mul-
USENIX Association
USENIX Association
tiple children this way. To synchronize with a child and
collect its results, the parent calls Get with the Merge op-
tion, which merges all changes the child made to shared
memory, since its snapshot was taken, back into the par-
ent. If both parent and child—or the child and other chil-
dren whose changes the parent has collected—have con-
currently modified the same byte since the snapshot, the
kernel detects and reports this conflict.
Our runtime also supports barriers, the foundation of
data-parallel programming models like OpenMP [45].
When each thread in a group arrives at a barrier, it calls
Ret to stop and wait for the parent thread managing
the group. The parent calls Get with Merge to collect
each child’s changes before the barrier, then calls Put
with Copy and Snap to resume each child with a new
shared memory snapshot containing all threads’ prior re-
sults. While our private workspace model conceptually
extends to non-hierarchical synchronization [5], our pro-
totype’s strict space hierarchy currently limits synchro-
nization flexibility, an issue we intend to address in the
future. Any synchronization abstraction may be emulated
at some cost as described in the next section, however.
An application can choose which parts of its address
space to share and which to keep thread-private. By plac-
ing thread stacks outside the shared region, all threads
can reuse the same stack area, and the kernel wastes no
effort merging stack data. Thread-private stacks also of-
fer the convenience of allowing a child thread to inherit
its parent’s stack, and run “inline” in the same C/C++
function as its parent, as in Figure 1. If threads wish
to pass pointers to stack-allocated structures, however,
then they may locate their stacks in disjoint shared re-
gions. Similarly, if the file system area is shared, then the
threads share a common file descriptor namespace as in
Unix. Excluding the file system area from shared space
and using normal file system reconciliation (Section 4.2)
to synchronize it yields thread-private file tables.
4.5 Emulating Legacy Thread APIs
As discussed in Section 2.3, we hope much existing se-
quential code can readily be parallelized using naturally
deterministic synchronization abstractions, like data-
parallel models such as OpenMP [45] and SHIM [24]
already offer. For code already parallelized using non-
deterministic synchronization, however, Determinator’s
runtime can emulate the standard pthreads API via deter-
ministic scheduling [8, 10,21], at certain costs.
In a process that uses nondeterministic synchroniza-
tion, the process’s initial master space never runs ap-
plication code directly, but instead acts as a determin-
istic scheduler. This scheduler creates one child space
to run each application thread. The scheduler runs the
threads under an artificial execution schedule, emulating
a schedule by which a true shared-memory multiproces-
sor might in principle run them, but using a deterministic,
virtual notion of time—namely, number of instructions
executed—to schedule all inter-thread interactions.
Like DMP [8, 21], our deterministic scheduler guan-
tizes each thread’s execution by preempting it after exe-
cuting a fixed number of instructions. Whereas DMP im-
plements preemption by instrumenting user-level code,
our scheduler uses the kernel’s instruction limit feature
(Section 3.2). The scheduler “donates” execution quanta
to threads round-robin, allowing each thread to run con-
currently with other threads for one quantum, before col-
lecting the thread’s shared memory changes via Merge
and restarting it for another quantum.
A thread’s shared memory writes propagate to other
threads only at the end of each quantum, violating se-
quential consistency [38]. Like DMP-B [8], our sched-
uler implements a weak consistency model [28], totally
ordering only synchronization operations. To enforce
this total order, each synchronization operation could
simply spin for a a full quantum. To avoid wasteful
spinning, however, our synchronization primitives inter-
act with the deterministic scheduler directly.
Each mutex, for example, is always “owned” by some
thread, whether or not the mutex is locked. The mutex’s
owner can lock and unlock the mutex without scheduler
interactions, but any other thread needing the mutex must
first invoke the scheduler to obtain ownership. At the
current owner’s next quantum, the scheduler “steals” the
mutex from its current owner if the mutex is unlocked,
and otherwise places the locking thread on the mutex’s
queue to be awoken once the mutex becomes available.
Since the scheduler can preempt threads at any
point, a challenge common to any preemptive sce-
nario is making synchronization functions such as
pthread_mutex_lock() atomic. The kernel does
not allow threads to disable or extend their own instruc-
tion limits, since we wish to use instruction limits at pro-
cess level as well, e.g., to enforce deterministic “time”
quotas on untrusted processes, or to improve user-level
process scheduling (see Section 4.1) by quantizing pro-
cess execution. After synchronizing with a child thread,
therefore, the master space checks whether the instruc-
tion limit preempted a synchronization function, and if
so, resumes the preempted code in the master space. Be-
fore returning to the application, these functions check
whether they have been “promoted” to the master space,
and if so migrate their register state back to the child
thread and restart the scheduler in the master space.
While deterministic scheduling provides compatibility
with existing parallel code, it has drawbacks. The master
space, required to enforce a total order on synchroniza-
tion operations, may be a scaling bottleneck unless exe-
cution quanta are large. Since threads can interact only
at quanta boundaries, however, large quanta increase the
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI’10) 201
202
time one thread may waste waiting to interact with an-
other, to steal an unlocked mutex for example.
Further, since the deterministic scheduler may pre-
empt a thread and propagate shared memory changes at
any point in application code, the programming model
remains nondeterministic. In contrast with our private
workspace model, if one thread runs ‘x = y’ while an-
other runs ‘y = x’ under the deterministic scheduler, the
result may be repeatable but is no more predictable to the
programmer than on traditional systems. While rerun-
ning a program with exactly identical inputs will yield
identical results, if the input is perturbed to change the
length of any instruction sequence, these changes may
cascade into a different execution schedule and trigger
schedule-dependent if not timing-dependent bugs.
5 Prototype Implementation
Determinator is written in C with small assembly frag-
ments, currently runs on the 32-bit x86 architecture, and
implements the kernel API and user-level runtime facil-
ities described above. Source releases are available at
‘http: //dedis.cs.yale.edu/’.
Since our focus is on parallel compute-bound applica-
tions, Determinator’s I/O capabilities are currently lim-
ited. The system provides text-based console I/O and a
Unix-style shell supporting redirection and both scripted
and interactive use. The shell offers no interactive job
control, which would require currently unimplemented
“nondeterministic privileges” (Section 4.1). The system
has no demand paging or persistent disk storage: the
user-level runtime’s logically shared file system abstrac-
tion currently operates in physical memory only.
The kernel supports application-transparent space mi-
gration among up to 32 machines in a cluster, as de-
scribed in Section 3.3. Migration uses a synchronous
messaging protocol with only two request/response types
and implements almost no optimizations such as page
prefetching. The protocol runs directly atop Ethernet,
and is not intended for Internet-wide distribution.
The prototype has other limitations already men-
tioned. The kernel’s strict space hierarchy could bottle-
neck J/O-intensive applications (Section 3.1), and does
not easily support non-hierarchical synchronization such
as queues or futures (Section 4.4). The file system’s size
is constrained to a process’s address space (Section 4.2),
and special I/O files can fill up (Section 4.3). None of
these limitations are fundamental to Determinator’s pro-
gramming model. At some cost in complexity, the model
could support non-hierarchical synchronization [5]. The
runtime could store files in child spaces or on external
I/O devices, and could garbage-collect I/O streams.
Implementing instruction limits (Section 3.2) requires
the kernel to recover control after a precise number of
instructions execute in user mode. While the PA-RISC
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
architecture provided this feature [1], the x86 does not,
so we borrowed ReVirt’s technique [22]. We first set an
imprecise hardware performance counter, which unpre-
dictably overshoots its target a small amount, to interrupt
the CPU before the desired number of instructions, then
run the remaining instructions under debug tracing.
6 Evaluation
This section evaluates the Determinator prototype, first
informally, then examining single-node and distributed
parallel processing performance, and finally code size.
6.1 Experience Using the System
We find that a deterministic programming model sim-
plifies debugging of both applications and user-level
runtime code, since user-space bugs are always repro-
ducible. Conversely, when we do observe nondetermin-
istic behavior, it can result only from a kernel (or hard-
ware) bug, immediately limiting the search space.
Because Determinator’s file system holds a process’s
output until the next synchronization event (often the
process’s termination), each process’s output appears
as a unit even if the process executes in parallel with
other output-generating processes. Further, different pro-
cesses’ outputs appear in a consistent order across runs,
as if run sequentially. (The kernel provides a system call
for debugging that outputs a line to the “real” console im-
mediately, reflecting true execution order, but chaotically
interleaving output as in conventional systems.)
While race detection tools exist [25,43], we found it
convenient that Determinator always detects conflicts un-
der “normal-case” execution, without requiring the user
to run a special tool. Since the kernel detects shared
memory conflicts and the user-level runtime detects file
system conflicts at every synchronization event, Deter-
minator’s model makes conflict detection as standard as
detecting division by zero or illegal memory accesses.
A subset of Determinator doubles as PJOS, “Paral-
lel Instructional Operating System,” which we used in
Yale’s operating system course this spring. While the
OS course’s objectives did not include determinism, they
included introducing students to parallel, multicore, and
distributed operating system concepts. For this purpose,
we found Determinator/PIOS to be a useful instructional
tool due to its simple design, minimal kernel API, and
adoption of distributed systems techniques within and
across physical machines. PIOS is partly derived from
MIT’s JOS [35], and includes a similar instructional
framework where students fill in missing pieces of a
“skeleton.” The twelve students who took the course,
working in groups of two or three, all successfully reim-
plemented Determinator’s core features: multiproces-
sor scheduling with Get/Put/Ret coordination, virtual
memory with copy-on-write and Snap/Merge, user-level
USENIX Association
USENIX Association
~ 2.5
z 2
zl
. 1.5
$
ol
7 Cad AR
oO matmult blacks choles lu_cont
md5 qsort lu_noncont
Benchmark
1CPU M2CPUs M4CPUs M8CPUs M12 CPUs
Figure 7: Determinator performance relative to pthreads
under Ubuntu Linux on various parallel benchmarks.
threads with fork/join synchronization (but not determin-
istic scheduling), the user-space file system with ver-
sioning and reconciliation, and application-transparent
cross-node distribution via space migration. In their fi-
nal projects they extended the OS with features such as
graphics, pipes, and a remote shell. While instructional
use by no means indicates a system’s real-world utility,
we find the success of the students in understanding and
building on Determinator’s architecture promising.
6.2 Single-node Multicore Performance
Since Determinator runs user-level code “natively” on
the hardware instead of rewriting user code [8,21], we
expect it to perform comparably to conventional systems
when executing single-threaded, compute-bound code.
Since thread interactions require system calls, context
switches, and virtual memory operations, however, we
expect determinism to incur a performance cost in pro-
portion to the frequency of thread interaction.
Figure 7 shows the performance of several shared-
memory parallel benchmarks we ported to Determina-
tor, relative to the same benchmarks using conventional
pthreads on 32-bit Ubuntu Linux 9.10. The md5 bench-
mark searches for an ASCII string yielding a particu-
lar MDS5 hash, as in a brute-force password cracker;
matmult multiplies two 1024 x 1024 integer matrices;
qsort is a recursive parallel quicksort on an integer ar-
ray; blackscholes is a financial benchmark from the PAR-
SEC suite [11]; and fft, Ju_cont, and lu_noncont are Fast
Fourier Transform and LU-decomposition benchmarks
from SPLASH-2 [57]. We tested all benchmarks on a
2 socket x 6 core, 2.2GHz AMD Opteron PC.
Coarse-grained benchmarks like md5, matmult, qsort,
blackscholes, and fft show performance comparable with
that of nondeterministic multithreaded execution under
Linux. The md5 benchmark shows better scaling on De-
terminator than on Linux, achieving a 2.25x speedup
over Linux on 12 cores. We have not identified the pre-
cise cause of this speedup over Linux but suspect scaling
bottlenecks in Linux’s thread system [54].
12
A 10 ideal
OC ¢ =md5
. ==blackscholes
2 6 ==matmult
a 4 om fft
= == qsort
8 2 == |u_cont
2 =
a o lu_noncont
0 2 4 6 8 10 12
Figure 8: Determinator parallel speedup over its own
single-CPU performance on various benchmarks.
x -
5 25
2
oO
7 1.5
05 ‘ima ‘he
3 0 L
na 32x32 128x128 512x512
16x16 64x64 256x256 1024x1024
Matrix size
1CPU M2CPUs M4CPUs M8&CPUs M12 CPUs
Figure 9: Matrix multiply with varying matrix size.
Porting the blackscholes benchmark to Determinator
required no changes as it uses deterministically sched-
uled pthreads (Section 4.5). The deterministic sched-
uler’s quantization, however, incurs a fixed performance
cost of about 35% for the chosen quantum of 10 million
instructions. We could reduce this overhead by increas-
ing the quantum, or eliminate it by porting the bench-
mark to Determinator’s “native” parallel API.
The fine-grained /u benchmarks show a higher per-
formance cost, indicating that Determinator’s virtual
memory-based approach to enforcing determinism is not
well-suited to fine-grained parallel applications. Future
hardware enhancements might make determinism practi-
cal for fine-grained parallel applications, however [21].
Figure 8 shows each benchmark’s speedup relative to
single-threaded execution on Determinator. The “embar-
rassingly parallel” md5 and blackscholes scale well, mat-
mult and fft level off after four processors (but still per-
form comparably to Linux as Figure 7 shows), and the
remaining benchmarks scale poorly.
To quantify further the effect of parallel interaction
granularity on deterministic execution performance, Fig-
ures 9 and 10 show Linux-relative performance of mat-
mult and qsort, respectively, for varying problem sizes.
With both benchmarks, deterministic execution incurs a
high performance cost on small problem sizes requiring
frequent interaction, but on large problems Determinator
is competitive with and sometimes faster than Linux.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) = 203
204
8
6
4
2
1
0.8
02° St call call ill
1K 2K 5K 10K 20K 50K 100K 200K 500K 1M
Array size (number of elements)
M2CPUs M4CPUs M8CPUs HM 12 CPUs
Speedup over Linux
oO
Oo
Figure 10: Parallel quicksort with varying array size.
c
2
3
S 10
oi
“3
©
8° = ideal
3 1 == md5-tree
a= == md5-circuit
2 == matmult-tree
9S
2 0.1
no 1 10
Number of nodes (log scale)
Figure 11: Speedup of deterministic shared memory
benchmarks on varying-size distributed clusters.
6.3 Distributed Computing Performance
While Determinator’s rudimentary space migration (Sec-
tion 3.3) is far from providing a full cluster comput-
ing architecture, we would like to test whether such a
mechanism can extend a deterministic computing model
across nodes with usable performance at least for some
applications. We therefore changed the md5 and mat-
mult benchmarks to distribute workloads across a clus-
ter of up to 32 uniprocessor nodes via space migration.
Both benchmarks still run in a (logical) shared memory
model via Snap/Merge. Since we did not have a clus-
ter on which we could run Determinator natively, we ran
it under QEMU [6], on a cluster of 2 socket x 2 core,
2.4GHz Intel Xeon machines running SuSE Linux 11.1.
Figure 11 shows parallel speedup under Determinator
relative to local single-node execution in the same envi-
ronment, on a log-log scale. In md5-circuit, the master
space acts like a traveling salesman, migrating serially to
each “worker” node to fork child processes, then retrac-
ing the same circuit to collect their results. The md5-tree
variation forks workers recursively in a binary tree: the
master space forks children on two nodes, those children
each fork two children on two nodes, etc. The matmult-
tree benchmark implements matrix multiply with recur-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
x 2
=
Cc
— 15
D 1 node
31 i 2 nodes
2 o5 i 4 nodes
3 . 8 nodes
Q 0 i 16 nodes
n md5-tree md5-circuit matmult-tree
Benchmark
Figure 12: Deterministic shared memory benchmarks
versus distributed-memory equivalents for Linux.
Determinator PIOS
Component Semicolons | Semicolons
Kernel core 2044 1847
Hardware/device drivers 751 647
User-level runtime 2952 1079
Generic C library code 6948 394
User-level programs 1797 1418
Total 14,492 5385
Table 3: Implementation code size of the Determinator
OS and of PIOS, its instructional subset.
sive work distribution as in md5-tree.
The “embarrassingly parallel” md5-tree performs and
scales well, but only with recursive work distribution.
Matrix multiply levels off at two nodes, due to the
amount of matrix data the kernel transfers across nodes
via its simplistic page copying protocol, which currently
performs no data streaming, prefetching, or delta com-
pression. The slowdown for 1-node distributed execution
in matmult-tree reflects the cost of transferring the matrix
to a (single) remote machine for processing.
As Figure 12 shows, the shared memory md5-tree
and matmult-tree benchmarks, running on Determina-
tor, perform comparably to nondeterministic, distributed-
memory equivalents running on Puppy Linux 4.3.1, in
the same QEMU environment. Determinator’s clustering
protocol does not use TCP as the Linux-based bench-
marks do, so we explored the benchmarks’ sensitivity
to this factor by implementing TCP-like round-trip tim-
ing and retransmission behavior in Determinator. These
changes resulted in less than a 2% performance impact.
Illustrating the simplicity benefits of Determinator’s
shared memory thread API, the Determinator version of
md5 is 63% the size of the Linux version (62 lines con-
taining semicolons versus 99), which uses remote shells
to coordinate workers. The Determinator version of mat-
mult is 34% the size of its Linux equivalent (90 lines ver-
sus 263), which passes data explicitly via TCP.
6.4 Implementation Complexity
To provide a feel for implementation complexity, Table 3
shows source code line counts for Determinator, as well
as its PIOS instructional subset, counting only lines con-
USENIX Association
USENIX Association
taining semicolons. The entire system is less than 15,000
lines, about half of which is generic C and math library
code needed mainly for porting Unix applications easily.
7 Related Work
Recognizing the benefits of determinism [12,40], paral-
lel languages such as SHIM [24] and DPJ [12, 13] en-
force determinism at language level, but require rewrit-
ing, rather than just parallelizing, existing serial code.
Race detectors [25, 43] detect low-level heisenbugs in
nondeterministic parallel programs, but may miss higher-
level heisenbugs [3]. Language extensions can dynami-
cally check determinism assertions [16, 49], but heisen-
bugs may persist if the programmer omits an assertion.
Early parallel Fortran systems [7,51], release con-
sistent DSM [2, 17], transactional memory [33,52] and
OS APIs [48], replicated file systems [53,55], and dis-
tributed version control [29] all foreshadow Determina-
tor’s private workspace programming model. None of
these precedents create a deterministic application pro-
gramming model, however, as is Determinator’s goal.
Deterministic schedulers such as DMP [8, 21] and
Grace [10] instrument an application to schedule inter-
thread interactions on a repeatable, artificial time sched-
ule. DMP isolates threads via code rewriting, while
Grace uses virtual memory as in Determinator. De-
veloped simultaneously with Determinator, dOS [9] in-
corporates a deterministic scheduler into the Linux ker-
nel, preserving Linux’s existing programming model and
API. This approach provides greater backward compati-
bility than Determinator’s clean-slate design, but makes
the Linux programming model no more semantically de-
terministic than before. Determinator offers new thread
and process models redesigned to eliminate conventional
data races, while supporting deterministic scheduling in
user space for backward compatibility.
Many techniques are available to log and replay non-
deterministic events in parallel applications [39, 46, 56].
SMP-ReVirt can log and replay a multiprocessor virtual
machine [23], supporting uses such as system-wide in-
trusion analysis [22,34] and replay debugging [37]. Log-
ging a parallel system’s nondeterministic events is costly
in performance and storage space, however, and usu-
ally infeasible for “normal-case” execution. Determi-
nator demonstrates the feasibility of providing system-
enforced determinism for normal-case execution, with-
out internal event logging, while maintaining perfor-
mance comparable with current systems at least for
coarse-grained parallel applications.
Determinator’s kernel design owes much to microker-
nels such as L3 [41]. An interesting contrast is with
the Exokernel approach [26], which is incompatible with
Determinator’s. System-enforced determinism requires
hiding nondeterministic kernel state from applications,
such as the physical addresses of virtual memory pages,
whereas exokernels deliberately expose this state.
8 Conclusion
While Determinator is only a proof-of-concept, it shows
that operating systems can offer a pervasively and nat-
urally deterministic application environment, avoiding
the introduction of data races in shared memory and file
system access, thread and process synchronization, and
throughout the API. Our experiments suggest that such
an environment can efficiently run coarse-grained paral-
lel applications, both on a single multicore machine and
across a cluster, though supporting fine-grained paral-
lelism efficiently may require hardware evolution.
Acknowledgments
We thank Zhong Shao, Ramakrishna Gummadi, Frans
Kaashoek, Nickolai Zeldovich, Sam King, and the OSDI
reviewers for their valuable feedback. We also thank
NSF for their support under grant CNS-1017206.
References
[1] PA-RISC 1.1 Architecture and Instruction Set Reference
Manual. Hewlett-Packard, Feb. 1994.
[2] C. Amza et al. TreadMarks: Shared memory computing
on networks of workstations. IEEE Computer, 29(2):18-
28, Feb. 1996.
[3] C. Artho, K. Havelund, and A. Biere. High-level data
races. In VVEIS, pages 82-93, Apr. 2003.
[4] A. Aviram et al. Determinating timing channels in com-
pute clouds. In CCSW, Oct. 2010.
[5] A. Aviram and B. Ford. Deterministic consistency, Feb.
2010. http://arxiv.org/abs/0912.0926.
[6] F. Bellard. QEMU, a fast and portable dynamic translator,
Apr. 2005.
[7] M. Beltrametti, K. Bobey, and J. R. Zorbas. The con-
trol mechanism for the Myrias parallel computer system.
Computer Architecture News, 16(4):21-30, Sept. 1988.
[8] T. Bergan et al. CoreDet: A compiler and runtime system
for deterministic multithreaded execution. In /5th ASP-
LOS, Mar. 2010.
[9] T. Bergan et al. Deterministic process groups in dOS. In
9th OSDI, Oct. 2010.
[10] E. D. Berger et al. Grace: Safe multithreaded program-
ming for C/C++. In OOPSLA, Oct. 2009.
[11] C. Bienia et al. The PARSEC benchmark suite: Charac-
terization and architectural implications. In 17th PACT,
October 2008.
[12] R. L. Bocchino et al. Parallel programming must be de-
terministic by default. In HotPar. Mar. 2009.
[13] R. L. Bocchino et al. A type and effect system for deter-
ministic parallel Java. In OOPSLA, Oct. 2009.
[14] S. Boyd-Wickizer et al. Corey: An operating system for
many cores. In Sth OSDI, Dec. 2008.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) 205
206
[15]
[16]
[17]
[18]
[19]
[20]
[21]
[22]
[23]
[24]
[25]
[26]
[27]
[28]
[29]
[30]
[31]
[32]
[33]
[34]
[35]
T. C. Bressoud and F. B. Schneider. Hypervisor-based
fault-tolerance. TOCS, 14(1):80-107, Feb. 1996.
J. Burnim and K. Sen. Asserting and checking determin-
ism for multithreaded programs. In FSE, Aug. 2009.
J. B. Carter, J. K. Bennett, and W. Zwaenepoel. Imple-
mentation and performance of Munin. In /3th SOSP, Oct.
1991.
M. Castro and B. Liskov. Practical byzantine fault toler-
ance. In 3rd OSDI, pages 173-186, Feb. 1999.
J.-D. Choi and H. Srinivasan. Deterministic replay of Java
multithreaded applications. In SPDT ’98: Proceedings of
the SIGMETRICS symposium on Parallel and distributed
tools, pages 48-59. 1998.
H. Cui, J. Wu, and J. Yang. Stable deterministic multi-
threading through schedule memoization. In 9th OSDI,
Oct. 2010.
J. Devietti et al. DMP: Deterministic shared memory mul-
tiprocessing. In 14th ASPLOS, Mar. 2009.
G. W. Dunlap et al. ReVirt: Enabling intrusion analysis
through virtual-machine logging and replay. In 5th OSDI,
Dec. 2002.
G. W. Dunlap et al. Execution replay for multiprocessor
virtual machines. In VEE, Mar. 2008.
S. A. Edwards, N. Vasudevan, and O. Tardieu. Program-
ming shared memory multiprocessors with determinis-
tic message-passing concurrency: Compiling SHIM to
Pthreads. In DATE, Mar. 2008.
D. Engler and K. Ashcraft. RacerX: effective, static de-
tection of race conditions and deadlocks. In 19th SOSP,
Oct. 2003.
D.R. Engler, M. F. Kaashoek, and J. O’ Toole. Exokernel:
An operating system architecture for application-level re-
source management. In /5th SOSP, Dec. 1995.
B. Ford et al. Microkernels meet recursive virtual ma-
chines. In 2nd OSDI, pages 137-151, 1996.
K. Gharachorloo et al. Memory consistency and event
ordering in scalable shared-memory multiprocessors. In
17th ISCA, pages 15-26, May 1990.
git: the fast version control system.
http://git-scm.com/.
A. Haeberlen et al. Accountable virtual machines. In 9th
OSDI, Oct. 2010.
A. Haeberlen, P. Kouznetsov, and P. Druschel. PeerRe-
view: Practical accountability for distributed systems. In
21st SOSP, Oct. 2007.
R. H. Halstead, Jr. Multilisp: A language for concur-
rent symbolic computation. TOPLAS, 7(4):501-538, Oct.
1985.
M. Herlihy and J. E. B. Moss. Transactional memory:
Architectural support for lock-free data structures. In 20th
ISCA, pages 289-300, May 1993.
A. Joshi et al. Detecting past and present intrusions
through vulnerability-specific predicates. In 20th SOSP,
pages 91-104. 2005.
F. Kaashoek et al. 6.828: Operating system engineering.
http://pdos.csail.mit.edu/6.828/.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
[36]
[37]
[38]
[39]
[40]
[41]
[42]
[43]
[44]
[45]
[46]
[47]
[48]
[49]
[50]
[51]
[52]
[53]
[54]
[55]
[56]
[57]
G. Kahn. The semantics of a simple language for paral-
lel programming. In Jnformation Processing, pages 471-—
475. 1974.
S. T. King, G. W. Dunlap, and P. M. Chen. Debugging
operating systems with time-traveling virtual machines.
In USENIX, pages 1-15, Apr. 2005.
L. Lamport. How to make a multiprocessor computer that
correctly executes multiprocess programs. [EEE Transac-
tions on Computers, 28(9):690-691, Sept. 1979.
T. J. Leblanc and J. M. Mellor-Crummey. Debugging par-
allel programs with instant replay. IEEE Transactions on
Computers, C-36(4):471-482, Apr. 1987.
E. Lee. The problem with threads. Computer, 39(5):33-
42, May 2006.
J. Liedtke. On micro-kernel construction. In /5th SOSP,
1995.
S. Lu, S. Park, E. Seo, and Y. Zhou. Learning from mis-
takes — a comprehensive study on real world concur-
rency bug characteristics. In /3th ASPLOS, pages 329-
339, Mar. 2008.
M. Musuvathi et al. Finding and reproducing Heisenbugs
in concurrent programs. In 8th OSDI. 2008.
M. Olszewski, J. Ansel, and S. Amarasinghe. Kendo: Ef-
ficient deterministic multithreading in software. In 14th
ASPLOS, Mar. 2009.
OpenMP Architecture Review Board. OpenMP applica-
tion program interface version 3.0, May 2008.
D. Z. Pan and M. A. Linton. Supporting reverse execution
of parallel programs. In PADD ’88, pages 124-129. 1988.
D. S. Parker, Jr. et al. Detection of mutual inconsistency
in distributed systems. IEEE Transactions on Software
Engineering, SE-9(3), May 1983.
D. E. Porter et al. Operating system transactions. In 22nd
SOSP, Oct. 2009.
C. Sadowski, S. N. Freund, and C. Flanagan. Single-
Track: A dynamic determinism checker for multithreaded
programs. In 18th ESOP, Mar. 2009.
F. B. Schneider. Implementing fault-tolerant services us-
ing the state machine approach: A tutorial. Computing
Surveys, 22(4):299-319, Dec. 1990.
J. T. Schwartz. The burroughs FMP machine, Jan. 1980.
Ultracomputer Note #5.
N. Shavit and D. Touitou. Software transactional memory.
Distributed Computing, 10(2):99-116, Feb. 1997.
D. B. Terry et al. Managing update conflicts in Bayou,
a weakly connected replicated storage system. In /5th
SOSP, 1995.
R. von Behren et al. Capriccio: Scalable threads for in-
ternet services. In SOSP’03.
B. Walker et al. The LOCUS distributed operating sys-
tem. OSR, 17(5), Oct. 1983.
L. Wittie. The Bugnet distributed debugging system. In
Making Distributed Systems Work, Sept. 1986.
S.C. Woo et al. The SPLASH-2 programs: Characteri-
zation and methodological considerations. In 22nd ISCA,
pages 24-36, June 1995.
USENIX Association
USENIX Association
Stable Deterministic Multithreading through Schedule Memoization
Heming Cui, Jingyue Wu, Chia-che Tsai, Junfeng Yang
{heming, jingyue, ct2459, junfeng + @cs.columbia.edu
Computer Science Department
Columbia University
New York, NY 10027
Abstract
A deterministic multithreading (DMT) system eliminates
nondeterminism in thread scheduling, simplifying the
development of multithreaded programs. However, ex-
isting DMT systems are unstable; they may force a pro-
gram to (ad)venture into vastly different schedules even
for slightly different inputs or execution environments,
defeating many benefits of determinism. Moreover, few
existing DMT systems work with server programs whose
inputs arrive continuously and nondeterministically.
TERN is a stable DMT system. The key novelty in
TERN is the idea of schedule memoization that memo-
izes past working schedules and reuses them on future
inputs, making program behaviors stable across different
inputs. A second novelty in TERN is the idea of win-
dowing that extends schedule memoization to server pro-
grams by splitting continuous request streams into win-
dows of requests. Our TERN implementation runs on
Linux. It operates as user-space schedulers, requiring no
changes to the OS and only a few lines of changes to the
application programs. We evaluated TERN on a diverse
set of 14 programs (e.g., Apache and MySQL) with real
and synthetic workloads. Our results show that TERN
is easy to use, makes programs more deterministic and
stable, and has reasonable overhead.
1 Introduction
Multithreaded programs are difficult to write, test, and
debug. A key reason is nondeterminism: different runs of
a multithreaded program may show different behaviors,
depending on how the threads interleave [35].
Two main factors make threads interleave nondeter-
ministically. The first is scheduling, how the OS and
hardware schedule threads. Scheduling nondeterminism
is not essential and can be eliminated without affecting
correctness for most programs. The second is input, what
data (input data) arrives at what time (input timing). In-
put nondeterminism sometimes is essential because ma-
jor changes in inputs require different schedules. How-
ever, frequently input nondeterminism is not essential
and the same schedule can be used to process many dif-
ferent inputs (§2.2). We believe nonessential nondeter-
minism should be eliminated in favor of determinism.
Deterministic multithreading (DMT) systems [13, 22,
41] make threads more deterministic by eliminating
scheduling nondeterminism. Specifically, they constrain
a multithreaded program such that it always uses the
same thread schedule for the same input. By doing so,
these systems make program behaviors repeatable, in-
crease testing confidence, and ease bug reproduction.
Unfortunately, though existing DMT systems elimi-
nate scheduling nondeterminism, they do not reduce in-
put nondeterminism. In fact, they may aggravate the ef-
fects of input nondeterminism because of their design
limitation: when scheduling the threads to process an
input, they consider only this input and ignore previ-
ous similar inputs. This stateless design makes schedules
over-dependent on inputs, so that a slight change to in-
puts may force a program to (ad)venture into a vastly dif-
ferent, potentially buggy schedule, defeating many bene-
fits of determinism. We call this the instability problem.
This problem is confirmed by our results (§8.2.1) from
an existing DMT system [13].
In fact, even with the same input, existing DMT sys-
tems may still force a program into different schedules
for minor changes in the execution environment such as
processor type and shared library. Thus, developers may
no longer be able to reproduce bugs by running their pro-
gram on the bug-inducing input, because their machine
may differ from the machine where the bug occurred.
This paper presents TERN, a schedule-centric, stateful
DMT system. It addresses the instability problem us-
ing an idea called schedule memoization that memoizes
past working schedules and reuses them for future inputs.
Specifically, TERN maintains a cache of past schedules
and the input constraints required to reuse these sched-
ules. When an input arrives, TERN checks the input
against the memoized constraints for a compatible sched-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 710) 207
208
similar inputs,
different schedules
aoe
similar inputs,
same schedule
mee
i / [
| |
} : } ; /
J /
same input,
different schedules
a
/
(a) nondeterministic (b) existing DMT systems (c) schedule memoization
Figure 1: Advantage of schedule memoization. Each solid
shape represents an input, and each curved line a schedule.
Schedule memoization reuses schedules when possible, avoid-
ing bugs in unknown schedules and making program behaviors
repeatable across similar inputs.
ule. If it finds one, it simply runs the program while
enforcing this schedule. Otherwise, it runs the program
to memoize a schedule and the input constraints of this
schedule for future reuse. By reusing schedules, TERN
avoids potential errors in unknown schedules. This ad-
vantage is illustrated in Figure 1.
A real-world analogy to schedule memoization is the
natural tendencies in humans and animals to follow fa-
miliar routes to avoid possible hazards along unknown
routes. Migrant birds, for example, often migrate along
fixed “flyways.” We thus name our system after the Arc-
tic Tern, a bird species that migrates the farthest among
all migrants [2].
A second advantage of schedule memoization is that
it makes schedules explicit, providing flexibility in de-
ciding when to memoize certain schedules. For instance,
TERN allows developers to populate a schedule cache of-
fline, to avoid the overhead of doing so online. Moreover,
TERN can check for errors (e.g., races) in schedules and
memoize only the correct ones, thus avoiding the buggy
schedules and amortizing the cost of checking for errors.
To make TERN practical, it must handle server pro-
grams which frequently use threads for performance.
These programs present two challenges for TERN: (1)
they often process client inputs (requests) as they arrive,
thus suffering from input timing nondeterminism, which
existing DMT systems do not handle and (2) they may
run continuously, making their schedules effectively in-
finite and too specific to reuse.
TERN addresses these challenges using a simple idea
called windowing. Our insight is that server programs
tend to return to the same quiescent states. Thus, TERN
splits the continuous request stream of a server into win-
dows and lets the server quiesce in between, so that
TERN can memoize and reuse schedules across windows.
Within a window, it admits requests only at fixed sched-
ule points, reducing timing nondeterminism.
We implemented TERN in Linux. It runs as “para-
sitic” user-space schedulers within the application’s ad-
dress space, overseeing the decisions of the OS sched-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
uler and synchronization library. It memoizes and reuses
synchronization orders as schedules to increase perfor-
mance and reuse rates. It tracks input constraints using
KLEE [17], a symbolic execution engine. Our implemen-
tation is software-only, works with general C/C++ pro-
grams using threads, and requires no kernel modifica-
tions and only a few lines of modification to applications,
thus simplifying deployment.
We evaluated TERN on a diverse set of 14 pro-
grams, including two server programs Apache [10] and
MySQL [4], a parallel compression utility PBZip2 [5],
and 11 scientific programs in SPLASH2 [6]. Our work-
load included a Columbia CS web trace and benchmarks
used by Apache and MySQL developers. Our results
show that
1. TERN is easy to use. For most programs, we modi-
fied only a few lines to adapt them to TERN.
2. TERN enforces stability across different inputs. In
particular, it reused 100 schedules to process 90.3%
of a 4-day Columbia CS web trace. Moreover, while
an existing DMT system [13] made three bugs in-
consistently occur or disappear depending on minor
input changes, TERN always avoided these bugs.
3. TERN has reasonable overhead. For nine out of four-
teen evaluated programs, TERN has negligible over-
head or improves performance; for the other pro-
grams, TERN has up to 39.1% overhead.
4. TERN makes threads deterministic. For twelve out
of fourteen evaluated programs, the schedules TERN
memoized can be deterministically reused barring the
assumption discussed in §7.
Our main conceptual contributions are that we identi-
fied the instability problem in existing DMT systems and
proposed two ideas, schedule memoization and window-
ing, to mitigate input nondeterminism. Our engineering
contributions include the TERN system and its evaluation
of real programs. To the best of our knowledge, TERN
is the first stable DMT system, the first to mitigate in-
put timing nondeterminism, and the first shown to work
on programs as large, complex, and nondeterministic as
Apache and MySQL. TERN demonstrates that DMT has
the potential to be deployed today.
This paper is organized as follows. We first present
a background (§2) and an overview of TERN (§3). We
then describe TERN’s interface (84), schedule memoiza-
tion for batch programs (§5), and windowing to extend
TERN to server programs (86). We then present refine-
ments we made to optimize TERN (87). Lastly, we show
our experimental results (88), discuss related work (89),
and conclude (810).
2 Background
This section presents a background of TERN. We explain
the instability problem of existing DMT systems (§2.1),
USENIX Association
USENIX Association
our choice of schedule representation in TERN (82.2),
and why we can reuse schedules across inputs (§2.3).
2.1. The Instability Problem
A DMT system is, conceptually, a function that maps an
input J to a schedule S. The properties of this function
are that the same J should map to the same S' and that
S is a feasible schedule for processing I. A stable DMT
system such as TERN has an additional property: it maps
similar inputs to the same schedule. Existing DMT sys-
tems, however, tend to map similar inputs to different
schedules, thus suffering from the instability problem.
We argue that this problem is inherent in existing
DMT systems because they are stateless. They must
provide the same schedule for an input across differ-
ent runs, using information only from the current run.
To force threads to communicate (e.g., acquire locks or
access shared memory) deterministically, existing DMT
systems cannot rely on physical clocks. Instead, they
maintain a logical clock per thread that ticks determin-
istically based on the code this thread has run. More-
over, threads may communicate only when their logical
clocks have deterministic values (e.g., smallest across the
logical clocks of all threads [41]). By induction, logical
clocks make threads deterministic.
However, the problem with logical clocks is that for
efficiency, they must tick at roughly the same rate to
prevent a thread with a slower clock from starving oth-
ers. Thus, existing DMT systems have to tie their logical
clocks to low-level instructions executed (e.g., completed
loads [41]). Consequently, a small change to the input or
execution environment may alter a few instructions exe-
cuted, in turn altering the logical clocks and subsequent
thread communications. That is, a small change to the
input or execution environment may cascade into a much
different (e.g., correct vs. buggy) schedule.
2.2 Schedule Representation and Determinism
Previous DMT systems have considered two types of
schedules: (1) a deterministic order of shared memory
accesses [13, 22] and (2) a synchronization order (i.e., a
total order of synchronization operations) [41]. The first
type of schedules are truly deterministic even if there are
races, but they are costly to enforce on commodity hard-
ware (e.g., up to 10 times overhead [13]). The second
type can be efficiently enforced (e.g., 16% overhead [41])
because most code is not synchronization code and can
run in parallel; however, they are deterministic only for
inputs that lead to race-free runs [41, 46].
TERN represents schedules as synchronization orders
for efficiency. An additional benefit is that synchroniza-
tion orders can be reused more frequently than memory
access orders (cf next subsection). Moreover, researchers
have found that many concurrency errors are not data
Program Input Constraints for Schedule Reuse
PBZip2 Same number of file blocks (NumBlocks
or —b) and threads (—p)
Apache For groups of typical HTTP GET requests,
same cache status and response sizes
fft Same number of threads (—p)
lu Same number of threads (—p), size of the
matrix (—n), and block size (—b)
barnes Same number of threads (NPROC) and val-
ues of variables dt ime and tstop
Table 1: Input constraints of five programs to reuse schedules.
Identifiers without a dash are configuration variables, and those
with a dash are command line options.
races, but atomicity and order violations [39]. These er-
rors can be deterministically reproduced or avoided using
only synchronization orders.
Although data races may still make runs which reuse
schedules nondeterministic, TERN is less prone to this
problem than existing DMT systems [41] because it has
the flexibility to select schedules. If it detects a race in
a memoized schedule, it can simply discard this sched-
ule and memoize another. This selection task is often
easy because most schedules are race-free. In rare cases,
TERN may be unable to find a race-free schedule, result-
ing in nondeterministic runs. However, we argue that in-
put nondeterminism cannot be fully eliminated anyway,
so we may as well tolerate some scheduling nondeter-
minism, following the end-to-end argument.
2.3. Why Can We Reuse Schedules?
This subsection presents an intuitive and an empirical
argument to support our insight that we can frequently
reuse schedules for many programs/workloads. Intu-
itively, synchronization operations map to developer in-
tents of inter-thread control flow. By enforcing the
same synchronization order, we fix the same inter-thread
“path,” but still allow many different inputs to flow down
this path. (This observation is similarly made for sequen-
tial paths [11, 12, 26].)
To empirically validate our insight, we studied the
input constraints to reuse schedules for five programs,
including a parallel compression utility PBZip2; the
Apache web server; and three scientific programs fft, lu,
and barnes in SPLASH2. Table 1 shows the results for
all programs studied. We found that the input constraints
were often general, allowing frequent reuses of sched-
ules. For instance, PBZip2 can use the same schedule to
compress many different files, as long as the number of
threads and the number of file blocks remain the same.
3 Overview
Our design of TERN adheres to the following goals:
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) 209
210
Compile Time Runtime
|
|
|
Program | 1, Si _#Hit
Source | ae meee
| / \
vy <C v
| Program Program
Compiler
| Replayer |Memoizer
Instrumentor \
| Os Os
\
Schedule Cache
Figure 2: TERN architecture. Its components are shaded.
1. Backward compatibility. We design TERN for gen-
eral multithreaded programs because of their domi-
nance in parallel programs today and likely tomor-
row. We design TERN to run in user-space and on
commodity hardware to ease deployment.
2. Stability. We design TERN to bias multithreaded
programs toward repeating their past, familiar sched-
ules, instead of venturing into unfamiliar ones.
3. Efficiency. We design TERN to be efficient because
it operates during the normal executions of programs,
not replayed executions.
4. Best-effort determinism. We design TERN to make
threads deterministic, but we sacrifice determinism
when it contradicts the preceding goals.
The remaining of this section presents TERN’s archi-
tecture (§3.1), workflow (§3.2), deployment scenarios
(83.3), and limitations (§3.4).
3.1 Architecture
Figure 2 shows the architecture of TERN and its five
components: instrumentor, schedule cache, proxy, re-
player, and memoizer. To use TERN, developers first
annotates their application by marking the input data
that may affect synchronization operations. They then
compile their program with the instrumentor, which
intercepts standard synchronization operations such as
pthread_mutex_lock() so that at runtime TERN
can control these operations. (We describe additional an-
notations and instrumentations that TERN needs in 84).
The instrumentor runs as a plugin to LLVM [3], requir-
ing no modifications to the compiler.
The schedule cache stores all memoized schedules and
their input constraints. This cache can be marshalled to
disk and read back upon program start, so that it need
not be repopulated. Each memoized schedule is concep-
tually a tuple (C, S'), where S is a synchronization order
and Cis the set of input constraints required to reuse S.
(We explain the actual representation in 85.2).
At runtime, once an input J arrives, the proxy in-
tercepts the input and queries the schedule cache for a
constraint-schedule tuple (C;,.5;) such that I satisfies
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
1: main(int argc, char *argv[]) {
28 int i, nthread = argv[1], nblock = argv[2];
3.2 symbolic(&nthread, sizeof(int)); // mark input data
4: symbolic(&nblock, sizeof(int)); // that affects schedules
5: for(i=0; i<nthread; ++i)
6: pthread_create(worker); // create worker threads
Ls for(i=0; i<nblock; ++i) {
Oo block = read_block(i); // read i’th file block
93 worklist.add(block); // add block to work list
10: }
11:
12: worker() { // worker threads for compressing file blocks
13: for(;;) {
14: block = worklist.get(); // get a file block from work list
15: compress(block);
16: }
17: }
Figure 3: Simplified PBZip2 code.
C;. Ona cache hit, the proxy lets the replayer run the
program on input J and enforce schedule S;. On a cache
miss, it lets the memoizer run the program on input J to
memoize a new schedule.
During a memoization run, the memoizer records all
synchronization operations into a schedule S. It also
computes C’, the input constraints for reusing S’,, via sym-
bolic execution [17]. The basic idea of symbolic execu-
tion is to track the outcomes of branches that observe
symbolic data, in our case, the data marked by develop-
ers as affecting synchronizations. Once the memoization
run ends, the set of branch outcomes we collected de-
scribes the input constraints needed to reuse the memo-
ized schedule.
For determinism, the memoizer can optionally check
a memoization run for data races. If it detects no races, it
simply stores (C,, 5’) into the schedule cache. Otherwise,
it can discard the memoized schedule and rerun the pro-
gram with a different scheduling algorithm to memoize
another schedule.
The proxy performs an additional task for server pro-
grams to reduce input timing nondeterminism and to
reuse schedules for these programs. Specifically, it
buffers the requests of a server into a window with a fixed
size. When the window becomes full, or remains partial
for a predefined timeout, TERN runs the server to process
the window as if the server were a batch program. It then
lets the server quiesce before moving to the next window
to avoid interference between windows.
3.2 Workflow and An Example
We illustrate how TERN works using PBZip2 as an ex-
ample. Figure 3 shows the simplified code of PBZip2.
Variables nthread and nblock affect synchroniza-
tions, so developers mark them by calling the TERN-
provided method symbolic () (line 3 and line 4). This
code spawns nthread worker threads, splits the file
USENIX Association
USENIX Association
// main worker 1 worker 2
9: worklist.add();
14: worklist.get();
9: worklist.add();
14: worklist.get();
Figure 4: Synchronization order of a PBZip2 run.
5: 0 < nthread ? true
5: 1 < nthread ? true
5: 2 < nthread ? false
7: 0 < nblock ? true
7: 1 < nblock ? true
7: 2 < nblock ? false
Figure 5: Input constraints of a PBZip2 run.
into nblock blocks, and compresses them in parallel
by calling compress(). To coordinate the worker
threads, it uses a synchronized work list. (Note TERN
tracks low-level synchronizations such as pthread primi-
tives; we use a work list here only for clarity.)
Suppose we run PBZip2 with two threads on a two-
block file. Suppose the schedule cache is empty and
TERN runs the memoizer to memoize a new schedule.
As PBZip2 runs, TERN controls and records the synchro-
nization operations (line 9 and line 14). It also tracks
the outcomes of branch statements that observe symbolic
data (line 5 and line 7). At the end of the run, TERN
records a schedule as shown in Figure 4. It also col-
lects constraints as shown in Figure 5, which simplify
to nthread = 2 A nblock = 2.' It stores the schedule
and the input constraints into the schedule cache.
If we run PBZip2 again with two threads on a different
two-block file, TERN will check if variable nthread
and nblock satisfy any set of constraints in the schedule
cache. In this case, TERN will succeed. It will then reuse
the schedule (Figure 4) to compress the file, even though
the file data may differ completely.
3.3. Deployment Scenarios
We anticipate three ways users may deploy TERN to
make their programs stable and deterministic.
Schedule-carrying code. Developers pre-populate a
cache of correct, representative schedules on typical
workloads, then ship their program with the cache hard-
wired and marked read-only.
Online memoization. Users can turn on memoization
at their local sites so that TERN can memoize schedules
as the programs run on real inputs.
Shadow memoization. Since tracking input constraints
is slow, users can configure TERN to memoize schedules
asynchronously. Specifically, for an input that misses the
' Although in this example the constraints are collected from one
thread, TERN can actually collect constraints from multiple threads.
schedule cache, the proxy runs the program as is, while
forwarding a copy of the input to the memoizer.
Each deployment mode has pros and cons. The first
mode makes a program stable and deterministic across
different sites, but may react poorly to site-specific work-
loads. The second mode updates the schedule cache
based on site-specific workloads, but may be slow be-
cause memoization runs tend to be slow. The last ap-
proach avoids the slowdown, but allows a program to run
nondeterministically when an input misses the schedule
cache. For server programs with high performance re-
quirements, we recommend the first and the third modes.
3.4 Limitations
Determinism. TERN aims for best-effort determinism
for reasons discussed in §2.2. If TERN is unable to find
a race-free schedule for an input, the run may be nonde-
terministic. We foresee several strategies to handle this
corner case while adhering to the other goals of TERN.
For instance, we can instrument the program to fix the
detected races or apply one of the existing DMT algo-
rithms to resolve the races deterministically. The advan-
tage of combining these techniques with TERN is that
we apply these expensive techniques only to a small por-
tion of schedules, and use TERN to efficiently handle the
common case. We leave these ideas for future work.
Applicability. We anticipate our approach will work
well for many programs/workloads as long as (1) they
can benefit from determinism and stability, (2) their con-
straints can be tracked by TERN, (3) their schedules can
be frequently reused, and (4) if windowing is needed,
their inputs can be buffered. For programs/workloads
that violate these assumptions, TERN may work poorly.
These programs/workloads may include parallel simula-
tors that require nondeterminism for statistical results,
GUI programs that cannot buffer user actions for la-
tency reasons, randomly generated workloads that pre-
vent schedule reuses, and programs whose schedules de-
pend on floating point inputs (which cannot be tracked
by TERN’s underlying symbolic execution engine).
Manual annotation. TERN requires manual annota-
tions. However, this annotation overhead tends to be
small. (See $7.4 for how TERN reduces this overhead
and §8.1 for an evaluation of this overhead). This over-
head may be further reduced using simple static analysis.
4 Interface
Table 2 shows TERN’s annotation interface which de-
velopers and the instrumentor use to annotate multi-
threaded programs. The annotations fall into four cat-
egories: (1) symbolic() for marking data that may
affect schedules; (2) task boundary annotations for mark-
ing the beginning and end of logical tasks, in case threads
get reused for different logical tasks (86); (3) wrap-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI’10) = 211
212
Annotations Inserted by Semantics
symbulicGiala. Jen). Developer Marks data that may affect schedules. The memoizer tracks constraints on this
: data. The replayer checks this data against the memoized constraints.
begin_task() Developer Mark the beginning and end of a logical task. Often used to divide the executions
end_task() of threads in a pool into separate tasks (86).
lock_wrapper(/) Developer Synchronization wrappers. The memoizer intercepts these operations for
unlock_wrapper(/) or TERN memoizing schedules, and the replayer intercepts them for reusing schedules.
before_blocking() oe Inserted before and after blocking system calls. The memoizer logs the order of
after_blocking()
these calls. The replayer opportunistically enforces the same order of these calls.
Table 2: TERN interface. Some annotations are inserted by developers, and others are inserted by the instrumentor, indicated by
Column Inserted By. Both the memoizer and the replayer use this interface, but they implement this interface differently (§5).
pers to synchronization operations (more examples in the
next paragraph); and (4) hook functions inserted around
blocking system calls, which TERN memoizes because
blocking systems calls are natural scheduling points.
Currently TERN hooks 28 pthread operations (e.g.,
pthread_mutex_lock(), pthread_create(),
and pthread_cond_wait ()). It also handles com-
mon atomic operations such as atomic_dec() and
atomic_inc(). It hooks eight blocking system calls
(e.g., sleep(), accept(), recv(), select (),
and read()). These hooks are sufficient to run the
programs evaluated, and we can easily add more.
Developers manually insert annotations in the first two
categories. They also annotate custom synchronizations
(e.g., custom spin locks). TERN’s instrumentor automat-
ically hooks standard synchronization and blocking sys-
tem calls. These annotations allow TERN’s memoizer
and replayer to run as “parasitic” user-space schedulers
that oversee the scheduling decisions of the OS and syn-
chronization library, requiring no modifications to either.
5 Schedule Memoization
This section presents the idea of schedule memoiza-
tion in the context of batch programs. We describe
how TERN memoizes schedules (85.1), tracks input con-
straints (85.2), merges a schedule into the schedule cache
(§5.3), and reuses schedules (85.4).
5.1 Memoizing Schedules
To memoize schedules, the memoizer controls and logs
synchronization operations. By default, it uses a sim-
ple round-robin (RR) algorithm that forces each thread
to do synchronizations in turn. One advantage of this al-
gorithm is that independent sites may memoize the same
schedules, making program behaviors deterministic and
stable across sites.
The memoizer implements this algorithm by imple-
menting the wrapper functions in Table 2. Figure 6
shows the wrappers to pthread_mutex_lock () and
pthread_mutex_unlock(). The memoizer main-
tains a queue of active threads. Only the thread at the
head of the queue “has the turn” (line 4 and 14). Once
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
1: queue_t activeq, waitq[N];
2: pthread_mutex_lock_wrapper(pthread_mutex_t “mutex) {
3: retry:
4: while(self()!=activeq.head); // wait for our turn
5s if(!phtread_mutex_trylock(mutex)) { // mutex acquired
6: append(schedule, self()); // add tid to schedule
7: move(self(), activeq.tail); // give turn to next thread
8: return;
9:
10: move(self(), waitq[mutex].tail); // deterministically wait
11: goto retry; // wait for our turn again
12:
13: pthread_mutex_unlock_wrapper(pthread_mutex_t *mutex) {
14: while(self()!=activeq.head); // wait for our turn
15: pthread_mutex_unlock(mutex); // mutex released
16: wake_up(waitq[mutex].head); // deterministically wake up
17: append(schedule, self()); // add tid to schedule
18: move(self(), activeq.tail); // give turn to next thread
19: }
Figure 6: The memoizer’s round-robin scheduling algorithm.
the thread is done with the operation, it gives up the turn
by moving itself to the tail of the queue (line 7 and 18).
We explain three subtleties of the code. First, to avoid
the deadlock scenario when the head of the queue at-
tempts to grab an unavailable mutex, we call the non-
blocking lock operation instead of the blocking one (line
5). If the mutex is not available, the thread gives up its
turn and waits on a TERN-maintained wait queue (line
10). TERN uses its own wait queues to avoid nondeter-
ministic wakeup orders in pthread library. Second, we
log synchronizations (line 6 and line 17) only when the
thread has the turn, so that the log faithfully reflects the
actual order of synchronizations. Lastly, we maintain our
internal thread IDs to avoid nondeterminism in the OS
thread IDs across runs. Function self () returns this
internal ID for the current thread (line 6 and line 17).
The memoizer allows a thread to break out of the
round-robin when the thread has waited for its turn for
over a second. The rationale is that if a thread has waited
too long, the current schedule will likely perform poorly
in reuse runs. However, such timeouts do not affect non-
determinism, because the memoizer still logs the order of
USENIX Association
USENIX Association
the occurred operations and the replayer simply enforces
the same order. In our experiments, we never observed
such timeouts because most threads synchronize or call
blocking system calls frequently.
Unlike previous DMT systems, TERN has the flexibil-
ity to select scheduling algorithms. In addition to the RR
algorithm, it implements a first-come first-served (FCFS)
algorithm that lets threads run as is. If the memoizer de-
tects a race using RR, it can restart the run and switch to
FCES. Implementing FCFS requires only minor modifi-
cations to the algorithm presented in Figure 6. Specifi-
cally, we replace line 4 and line 14 with a lock operation;
line 7, line 10, and line 18 with an unlock operation; and
line 16 a NOP.
In addition to synchronizations, the memoizer in-
cludes the hooks around blocking system calls (§4) in
the schedule it memoizes because blocking system calls
are natural scheduling points. However, the replayer will
only opportunistically replay these hooks when reusing a
schedule because the returns from blocking system calls
are driven by the program’s environment.
5.2 Tracking Input Constraints
Given the symbolic data marked by developers, the mem-
oizer tracks the constraints on this data by tracking (1)
what data is derived from the symbolic data and (2) the
outcomes of the branch statements that observe this sym-
bolic and derived data. At the end of this memoiza-
tion run, the set of branch outcomes together describe
the constraints to place on the symbolic data required to
reuse the memoized schedule. That is, if an input satis-
fies these constraints, we can re-run the program in the
same way as the memoization run. The constraints col-
lected this way may be over-constraining if developers
annotate too much data as symbolic. We describe a tech-
nique to address this problem in $7.4.
TERN leverages KLEE [17], an open-source symbolic
execution engine to track input constraints. To adapt
KLEE to TERN, we made two key modifications. First,
KLEE works only with sequential programs, thus we ex-
tended it to support threads. Specifically, we modified
KLEE to spawn a new KLEE instance for each new thread.
At the end of the run, we unify the constraints collected
from each thread as the input constraints of the schedule.
Second, we simplified KLEE to only collect constraints
without solving them, because unlike KLEE, TERN need
not explore different execution paths.
5.3 Merging Schedules into the Schedule Cache
Once TERN memoized a schedule S and its constraints
C’, TERN stores the tuple into the schedule cache. Al-
though the schedule cache is conceptually a set of (C’, S)
tuples, its actual structure is a decision tree because a
program may incrementally read inputs from its environ-
Program start
symbolic(input1, len1) C1
vsici S xy \
symbolic(input2, len2) C2
Vv s2 c2 oy Ne a
symbolic(input3, len3) C3
¥ $3.63 m7 TI \
(Aitweatset) Ch OA a ad
A schedule and
its constraints
Schedue Cache
Figure 7: Decision tree of TERN’s schedule cache.
ment, calling symbolic() multiple times. For exam-
ple, the code in Figure 3 calls symbolic () twice.
Figure 7 illustrates how TERN constructs the deci-
sion tree of the schedule cache. Given a (C,S) tuple,
TERN breaks it down to sub-tuples (C;, S;) separated by
symbolic () calls, where S; contains the synchroniza-
tion operations logged and C; contains the constraints
collected between the i*” and (i + 1)” symbolic ()
calls. It then merges the sub-tuples into the i*” level of
the decision tree.
TERN avoids merging redundant tuples into the cache.
That is, if the cache contains a tuple with less restrictive
constraints that the tuple being merged, TERN simply
discards the new tuple. Note that the tuples may overlap
(i.e., one input satisfies more than one set of constraints),
and TERN simply returns the first match if there are mul-
tiple matches.
To speed up cache lookup, TERN sorts all (C;, $;) tu-
ples within the same decision node based on their reuse
rates, defined as the number of successful reuses of S;
over the number of inputs that have satisfied C;. Reusing
a schedule may fail even if the input satisfies the sched-
ule’s input constraints (cf next subsection). However,
by sorting the tuples based on reuse rates, we automati-
cally prefer good schedules over bad ones that have many
failed reuse attempts. To bound the size of the sched-
ule cache, TERN can throw away bad schedules based on
reuse rates. However, we have not found the need to do
so because the schedule cache is often small.
5.4 Reusing Schedules
To reuse a schedule, TERN must check that the input sat-
isfies the input constraints of the schedule. To do so, it
maintains an iterator to the decision tree of the sched-
ule cache. The iterator starts from the root. As the pro-
gram runs and calls symbolic (), TERN moves the it-
erator down the tree. It checks if the data passed into a
symbolic () call satisfies any set of constraints stored
at the corresponding decision tree node and, if so, en-
forces the corresponding schedule.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) = 213
214
pthread_mutex_lock_wrapper(mutex) {
down(sem[self()]); // wait for our turn
pthread_mutex_lock(mutex);
next = shift schedule; // find next thread in schedule
up(sem[next]); // wake up next thread
ouarwWwNn—
Figure 8: Pseudo code of the replayer.
The performance of the replayer is crucial because
it runs during a program’s normal executions. To effi-
ciently enforce a synchronization order, the replayer uses
a technique we call semaphore relay. Specifically, the
replayer assigns each thread a semaphore. Before doing
a synchronization operation, a thread has to wait on its
semaphore for its turn. Once it is done with the oper-
ation, it passes the turn to the next thread in the sched-
ule by signaling the semaphore of the next thread. Com-
pared to an approach using locks or condition variables,
semaphore relay avoids unnecessary lock contentions.
Figure 8 illustrates semaphore relay using the replayer’s
pthread_mutex-_lock () wrapper.
We note several subtleties of the pseudo code in Fig-
ure 8. First, we do not use non-blocking lock operations
(line 3) as in Figure 6 because the memoizer only logs
successful lock acquisitions. Second, the replayer main-
tains internal thread IDs the same way as the memoizer
to avoid mismatches. Lastly, the down () (line 2) is ac-
tually a timed wait (with a default 0.1ms timeout), so that
a thread can break out of a schedule when the dynamic
load mismatches the schedule’s assumptions. Note that
these timeouts merely cause delays and do not affect cor-
rectness. They rarely occurred in our experiments.
6 Windowing
Server programs present two challenges for TERN. First,
they are more exposed to timing nondeterminism than
batch programs because their inputs (client requests) ar-
rive nondeterministically. Second, they often run contin-
uously, making their schedules too specific to reuse.
TERN addresses these challenges using a simple idea
called windowing. Our insight is that server programs
tend to return to the same quiescent states. Thus, in-
stead of processing requests as they arrive, TERN breaks
a continuous request stream down to windows of re-
quests. Within each window, it admits requests only at
fixed points in the current schedule. If no requests ar-
rive at an admission point for a predefined timeout, TERN
simply proceeds with the partial window. While a win-
dow is running, TERN buffers newly arrived requests so
that they do not interfere with the running window. With
this approach, TERN can memoize and reuse schedules
across (possibly partial) windows. The cost of window-
ing is that it may reduce concurrency and degrade server
throughput and speed. However, our experiments show
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
that this cost is reasonable and justified by the gain in
determinism and stability.
To buffer requests, TERN needs to know when a
server receives a request and when it is done process-
ing the request. Inferring these task boundaries based
on thread creation and exit is unreliable because server
programs frequently use thread pools. Thus, TERN cur-
rently lets developers annotate these boundaries using
begin_task () and end_task (). Manually locating
task boundaries is often easy: a request tends to begin
after an accept () of aclient connection and ends after
the server sends out a reply.
Exposing hidden states. The assumption of windowing
is that a server program returns to the same state when it
quiesces. However, in practice, server states evolve over
time. For instance, when Apache first serves a page, it
may load the page from disk and cache it in memory.
When this page is requested again, Apache can serve it
directly from its cache.
These state changes may affect schedules. In the ex-
ample above, Apache will perform different synchro-
nizations for the two runs. Thus, for TERN to accurately
select a schedule to reuse, it must know the hidden states
that affect schedules. Currently TERN lets developers
annotate such hidden states using symbolic(). Do-
ing so is often straightforward. For instance, we inserted
a symbolic() call to mark the return of Apache’s
cache_find() as symbolic.
Exposing hidden states may not always be easy.
We thus created a technique to tolerate missed
symbolic() annotations. The basic idea is to store
backup schedules under the same set of input constraints
to tolerate annotation inaccuracy. For instance, sup-
pose a symbolic () had not been missed, TERN would
have memoized two different constraint-schedule tuples
(C, $1) and (C2, 52). However, because of the missed
annotation, TERN missed the corresponding constraints,
wrongly collapsing C and C2 into the same set C.
Now the two original tuples become (CS) and (C, $2),
which appear redundant. Instead of discarding one of
these seemingly redundant schedules, TERN will store
both schedules with the same set of constraints. To se-
lect between these schedules, TERN can select the one
with higher reuse rate, which likely matches the hidden
state of the program.
7 Refinements
This section describes four refinements we made, one for
determinism (§7.1) and three for speed (§7.2-87.4).
7.1 Detecting Data Races
As discussed in §2.2, if a memoized schedule allows data
races, runs reusing this schedule may become nondeter-
ministic. Thus, for determinism, we would like to de-
USENIX Association
USENIX Association
1/ Ti 1 T2 1 Ti // T2
+4+X} lock(11);
lock(11); a{i]++; lock(12);
lock(12); a{j]——;
+4+X} unlock(11);
unlock(12);
Figure 9: A conventional
Figure 10: A symbolic race
race, not a schedule race.
that occurs only when i = j.
tect races in memoized schedules and discard them from
the schedule cache. A general race detector would flag
too many races for TERN because it detects conventional
races with respect to the original synchronization con-
straints of the program, whereas we want to detect races
with respect to the order constraints of a schedule [46]
(call them schedule races). Figure 9 shows a conven-
tional race, but not a schedule race because the synchro-
nization order shown “kills” the race.
Thus, we built a simple race detector to detect sched-
ule races. It runs with the memoizer and is happens-
before based. It considers one memory access happens
before another with respect to the synchronization order
the memoizer records. Sometimes a pair of instructions
may appear to be a race, when in fact their relative order
does not alter a run. For instance, a write-write race is
benign if both instructions write the same value. Simi-
larly, a read-write race is benign if the value written by
one instruction does not affect the value read by another.
Our race detector prunes these benign races.
Our detector also flags symbolic races, the races that
are data-dependent on inputs. Figure 10 shows an exam-
ple. Both variables 7 and 7 are inputs, and the race occurs
only when 2 = 7. The risk of a symbolic races is that it
may be absent in a memoization run and thus skip de-
tection, but show up nondeterministically in a reuse run.
To detect symbolic races, our race detector queries the
underlying symbolic execution engine for pointer equal-
ity. For example, to detect the race in Figure 10, it would
query the underlying symbolic execution engine for the
satisfiability of &a[i] = &alj]. It flags a symbolic race
if this constraint is satisfiable. Once a symbolic race is
flagged, TERN adds additional input constraints to ensure
that the race does not occur in reuse runs. For Figure 10,
we would add &aji] 4 &alj], which simplifies to i A j.
Our race detector can detect all schedule races in a
memoization run. It can also detect all symbolic races
if developers correctly annotate all data that affect syn-
chronization operations and memory locations accessed.
If this assumption holds and our race detector reports no
races in a memoization run, TERN ensures that the mem-
oized schedule can be deterministically reused.
7.2 Skipping Unnecessary Synchronizations
When reusing a schedule, TERN enforces a total syn-
chronization order according to the schedule. These
TERN-enforced execution order constraints are more
stringent than the constraints enforced by the origi-
nal synchronizations in the program. Thus, for speed,
TERN can actually skip these unnecessary synchroniza-
tions. In our current implementation, we skip sleep (),
usleep(), and pthread barrier _wait() be-
cause they are frequently used. We found that this op-
timization was quite effective and even made programs
run faster than nondeterministic execution (§8.3).
7.3 Simplifying Constraints
To reuse a schedule, TERN must check if the current in-
put satisfies the constraints of the schedule. The over-
head of this check depends on the number of constraints,
yet the set of constraints TERN collects may not always
be in simplified form. That is, a subset of the con-
straints may imply the entire set. For example, consider
aloop “for(int i=0;i!=n;++i)” witha symbolic
bound n. When running this code with n = 10, we will
collect a set of constraints {0 4 n,1 #4 n,...,10 = n},
but the last constraint alone implies the entire set.
To simplify constraints, TERN uses a greedy algo-
rithm. Given a set of constraints C’,, it iterates through
each constraint c, and checks if C/{c} implies {c}. If
so, it simply discards c. Our observation is that con-
straints collected later in a run tend to be more compact
than the earlier ones. Thus, when pruning constraints, we
start from the ones collected earlier. Although we could
have used the underlying symbolic execution engine to
simplify constraints, it lacks this domain knowledge and
may perform poorly.
7.4 Slicing Out Irrelevant Branches
A branch statement may observe a piece of symbolic
data but perform no synchronization operation in either
branch. The constraints collected from this branch are
unlikely to affect schedules. If we include irrelevant con-
straints in the input constraints of a schedule, we not only
increase constraint checking time, but also preclude legal
reuses of the schedule.
To address this problem, TERN employs a simple
static analysis to automatically prune likely irrelevant
constraints. At the heart of this technique is a slicing
analysis that identifies branch statements unlikely to af-
fect synchronization operations. Specifically, given a
branch statement s, this analysis computes sq, the im-
mediate post-dominator [8] of s, and marks s as irrele-
vant if no synchronization operations are between s and
sq. Although simple, this technique reduced constraint
checking time significantly (§8.3). However, we note
that our analysis is unsound because it ignores data de-
pendencies. Thus, we plan to implement a sound slicing
algorithm [21] in our future work.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) = 215
216
Program Size Symbolic Task Sync Total
Apache 464K +f 2 0 6 (+1)
MySQL 1,182K 1 2 0 3 (428)
PBZip2 1,551 3 N/A 0 3
fft 1,403 4 N/A 0 4
lu 1,265 3 N/A 0 3
barnes 1,954 9 N/A 0 9
radix 661 4 N/A 0 4
fmm 3,208 8 N/A 1 9
ocean 6,494 5 N/A 0 5
volrend 18,082 1 N/A 1 2
water-spatial 1,573 9 N/A 0 9
raytrace 5,808 3 N/A 0 3
water-nsquared 1,188 10 N/A 0 10
cholesky 3,683 3 N/A 1 4
Table 3: Statistics of programs evaluated. Size counts the
lines of code for each program. Symbolic counts the sym-
bolic variables we marked. Task counts the task boundary an-
notations (begin_task() and end_task ()) we inserted.
Sync counts the annotations for custom synchronizations we
inserted. The numbers in parenthesis under Total count mis-
cellaneous changes.
8 Evaluation
Our TERN implementation consists of 8,934 lines of C++
code, including 827 lines for the instrumentor imple-
mented as an LLVM pass; 5,451 lines for the proxy,
schedule cache, memoizer, and replayer; and 2,656 lines
for modifications to KLEE.
We evaluated TERN on a diverse set of 14 programs,
ranging from two server programs, Apache and MySQL,
to one parallel compression utility, PBZip2, to 11 scien-
tific programs in SPLASH2.”
Our main evaluation machine is a 2.66 GHz quad-core
Intel machine with 4 GB memory running Linux 2.6.24.
When evaluating TERN on server programs, we ran the
server on this machine and the client on another to avoid
unnecessary contention. These machines are connected
via |Gbps LAN. We compiled all programs down to ma-
chine code using Llvm-gcc -—0O2 and LLVM’s bitcode
compiler llc.
We focused our evaluation on four key questions:
1. Is TERN easy to use (88.1)?
2. Does TERN make multithreaded programs stable
across different inputs (§8.2)?
. Does TERN incur high overhead (88.3)?
4. Does TERN make multithreaded programs determin-
istic on the same input (88.4)?
8.1 Ease of Use
Table 3 summarizes the modifications we made to make
the programs work with TERN. For each program but
MySQL, we modified only 3-10 lines. For Apache, we
marked the HTTP command, URL, HTTP version, and
uo
>The version of the SPLASH2 [36] we acquired has 12 programs,
one of which does not compile on our evaluation machine.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
Nondet COREDET TERN
-p2 VV Vv vv ¥ *&€ HY Vv wv
-p4 ¥ Vv wo x *% Vv ¥ Vv wo
-ps ¥ Vv wo x x %& ¥ Vv wv
Args. | -m10 12) 14 -m10 12. 14 | -m10 12 14
Table 4: Bug stability results on SPLASH2 fft. The
leftmost column and the bottommost row show the com-
mand line arguments. Option -p specifies the number of
threads, and -m the amount of computation (matrix size).
Symbol % indicates that the bug occured, and W the bug
never occured.
the return of cache_find() as symbolic (86). For
MySQL, we marked the SQL query. For PBZip2, we
marked the number of threads and file blocks. (The num-
ber of file blocks is set in two places, contributing two
symbolic annotations.) For all these scientific programs,
we marked all input arguments as symbolic except those
configuring output verbosity.2 We marked three cus-
tom synchronization operations in three SPLASH2 pro-
grams. We made two miscellaneous changes to Apache
and MySQL. The line counts are shown in parenthesis
under the Total column. For Apache, we had to fix an
uninitialized memory read in ap_signal_server ()
to make it work with KLEE. For MySQL, we wrote a 28-
line function to mark the numbers in each SQL query as
concrete (i.e., not affecting schedules) to avoid making
the input constraints too specific.
8.2. Stability
We evaluated TERN’s stability via two sets of experi-
ments. The first set compares it to an existing DMT sys-
tem (88.2.1), the second quantifies how frequently it can
reuse schedules on real and synthetic workloads (88.2.2).
8.2.1 Bug Stability
We compared TERN to COREDET [13] in terms of bug
stability: does a bug occur in one run but disappear in an-
other when the input varies slightly? We ran three buggy
SPLASH2 programs, fft, lu, and barnes, in three modes:
nondeterministic execution (Nondet), with COREDET,
and with TERN. We varied their inputs by varying the
number of threads and the amount of computation. For
each program, execution mode, and input combination,
we ran the program 100 times, and recorded whether the
corresponding bug occurred.
We present only the fft results; the results of the other
programs are similar. Table 4 shows the buggy behav-
iors of fft. In nondeterministic mode, the bug never oc-
curred, despite that each run almost always yielded a new
synchronization order. With COREDET, slight changes
3Note that we could have used a two-line loop to mark these argu-
ments as symbolic. Instead, we report the total number of symbolic
variables to avoid masking real data.
USENIX Association
USENIX Association
Program-Workload Reuse Rates (%) Schedules
Apache-CS 90.3% 100
SysBench-simple 94.0% 50
SysBench-tx 44.2% 109
PBZip2-usr 96.2% 90
Table 5: TERN stability. Column Schedules indicates the
number of schedules in the schedule cache.
in computation made the bug occur or disappear. With
TERN, the bug never occurred, and TERN reused only
three schedules for all runs, one for each thread count.
8.2.2 Reuse Rates
We also quantified how frequently TERN could reuse
schedules. Specifically, we measured the overall reuse
rate, defined as the number of inputs processed using
memoized schedules over the total number of inputs. The
higher the reuse rates, the more stable the programs be-
come. TERN had nearly 100% overall reuse rates for the
scientific programs after a small number of memoization
runs. Thus, we focused on Apache, MySQL, and PBZip2
in out experiments.
We used four workloads to evaluate overall reuse rates:
Apache-CS: a real 4-day trace from the Columbia CS
website with 122,000 HTTP requests. We wrote a
script to replay this trace at a rate of 100 concurrent
requests per second.
SysBench-simple: SysBench [7] in simple mode. This
synthetic workload consists of random select queries.
SysBench-tx: SysBench in transaction mode. This syn-
thetic workload consists of random select, update,
delete, and insert queries.
PBZip2-usr: a random selection of 10,000 files from
/usvr on our evaluation machine.
For each workload, we first randomly selected 1%-3%
of the workload and ran the memoizer to populate the
schedule cache. We then ran the entire workload with
the replayer and measured the overall reuse rates. We
ran eight worker threads for each program because they
performed best (with or without TERN) with this setting.
Table 5 shows the results. For three out of the four
workloads, TERN could reuse a small number of sched-
ules to process over 90% of the inputs. For MySQL-
tx, TERN had a lower overall reuse rate. The reasons
are two fold. First, this workload makes it unlikely to
reuse schedules because it mixes many randomly gener-
ated queries with different types and parameters. Second,
we annotated only the SQL command as symbolic with-
out exposing the hidden states of MySQL (86) so that
we could measure TERN’s performance in an adversarial
setting. Nonetheless, TERN managed to process 44.2%
of inputs with a small number of schedules.
40
30
se
& 20
3
® 10
x
o
> 0
Oo
-10
-20
no x«K rot a
bE5RUe2SsESPERBE
owawh S&HKslsEuo
(ero oO a 9C a> Fo
oo ait Fy ORE
3569S af.
ELD o 5
qaQq25> = 2
qoeoeas g
Figure 11: Relative overhead of the replayer over nondeter-
ministic execution. Negative overhead means speedup.
8.3. Overhead
We used the following workloads to evaluate TERN’s
overhead. For Apache, we used ApacheBench [1] to re-
peatedly download a 50KB webpage. For MySQL, we
used the SysBench-simple workload from the previous
subsection. Both ApacheBench and SysBench are used
by the server developers themselves. We made these
benchmarks CPU bound by fitting the web or database
in memory and by connecting the server and client via a
1 Gbps LAN. For PBZip2, we decompressed a 10 MB
file. For SPLASH2 programs, we ran them typically for
10-100 ms. We measured the execution time for batch
programs and the throughput (TPUT) and response time
(RESP) for server programs. All numbers reported in
this section were averaged over 50 runs.
The most performance-critical component is the re-
player because it operates during the normal execu-
tion of a program. Figure 11 shows the relative over-
head of the replayer over nondeterministic execution,
the smaller the better. For seven out of the fourteen
programs, the replayer performed almost identically to
nondeterministic execution. For PBZip2 and barnes,
TERN performed better. This speedup came partially
from the optimization to remove unnecessary synchro-
nizations, discussed in the next paragraph. TERN’s
overhead for MySQL, volrend, raytrace, water-nsquared,
and choleskey is relatively large because these pro-
grams performed many synchronization operations over
a short period of time. For instance, water-nsquared
and cholesky both call pthread_mutex-_lock () and
pthread_mutex_unlock () ina tight loop.
We also measured the effects of skipping unneces-
sary synchronizations ($7.2). Figure 12 shows the re-
sults. This optimization significantly reduced the re-
player’s overhead for four programs. Specifically, it
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) 217
218
180: no opt
skip Sync ==
150 -
= 400 +
To
oO
oO
=
g
oO 50; oe
0 = a
-20 |
PBZip2 barnes’ volrend water-
nsquared
Figure 12: Overhead reduction by skipping unnecessary syn-
chronizations. “no opt” indicates the baseline overhead.
8.0 ;
no opt
w/ simplify 7.85
oC w/ slice
& w/ both sasamnnme
oO
£
oD
&
s% 0.8 7
ob)
=
S 06}
=
‘gS
a O4F
= acid
fo}
O 0.2 + [
ay
Apache fft lu radix
Figure 13: Optimizations to speed up constraint checking.
Note the y-axis is broken. “no opt” indicates the baseline con-
straint checking time. “simplify” refers to the optimization in
§7.3. “slice” refers to the optimization in §7.4.
made PBZip2 and barnes run faster than nondetermin-
istic execution, and reduced the overhead of water-
nsquared from 172.4% to 39.1%. Its effects on the other
programs are negligible and thus not shown.
To reuse a schedule on an input, TERN must check the
input against memoized constraints. Constraint check-
ing can be costly, and TERN provides two optimizations
to speed it up (87.3 and 87.4). Figure 13 shows these op-
timizations can effectively speed up constraint checking
for Apache, fft, lu, and radix. In particular, they reduced
the constraint checking time for lu by 16x.
Compared to the replayer, the memoizer can run of-
fline, thus its performance is not as critical. Table 6
shows that this slowdown can sometimes exceed 200x.
The main reason is that KLEE, the symbolic engine used,
interprets programs instead of running them natively. An
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
Program Nondet Memoization Overhead (times)
Apache-TPUT 462.2 req/s 2.1 req/s 219.1
Apache-RESP 0.22 s 3.96 s 17.0
MySQL-TPUT — 13779.3 req/s 172.2 req/s 79.0
MySQL-RESP 0.6 ms 61 ms 100.6
PBZip2 0.18 s 15.19 s 83.4
Table 6: Overhead of the memoizer.
Program Error Description
Apache Reference count decrement and check against
0 are not atomic.
PBZip2 Variable fifo is used in one thread after be-
ing freed by another.
fft initdonetime and finishtime are read
before assigned the correct values.
lu Variable rf is read before assigned the correct
value.
barnes Variable t rackt ime is read before assigned
the correct value.
Table 7: Concurrency errors used in evaluation.
instrumentation-based approach can greatly reduce this
slowdown [16], which we plan to implement in our fu-
ture work.
8.4 Determinism
We evaluated TERN’s determinism via three sets of ex-
periments. The first set checked the memoized schedules
for races (§8.4.1). The second evaluated TERN’s abil-
ity to deterministically reproduce or avoid bugs (88.4.2).
The third measured how deterministic memory accesses
are with and without TERN (88.4.3).
8.4.1 Race Detection Results
When memoizing schedules for each of the 14 programs,
we turned on TERN’s race detector. We found that except
for radix and cholesky, the schedules TERN memoized
for all other programs were free of schedule races and
symbolic races with respect to the symbolic data we an-
notated (§7.1). Our race detection result is not surprising
because most schedules are indeed race free. It implies
that, for runs that reuse the memoized schedules of all
programs but radix and cholesky, TERN ensures deter-
minism, barring the assumption discussed in 87.1.
8.4.2 Bug Determinism
We also evaluated how deterministically TERN could re-
produce or avoid bugs. Table 7 lists five real concur-
rency bugs we used. We selected them because they were
frequently used in previous studies [37, 39, 43, 44] and
we could reproduce them on our evaluation machine. To
measure bug determinism, we first memoized schedules
for programs listed in Table 7. We then manually inserted
usleep () to these programs to get alternate schedules.
USENIX Association
USENIX Association
Program Length Nondet TERN _ Ratio
Apache 148,058 86,215 10,821 7.97
PBZip2 1,234 161 69 2:33
Table 8: Memory access determinism. We traced memory ac-
cessed only from PBZip2, not the external BZip2 library.
We then ran the buggy programs again, reusing the mem-
oized schedules. We also injected random delays into the
reuse runs to perturb timing. We found that, TERN con-
sistently reproduced or avoided all five bugs. We verified
this result by inspecting the memoized schedules.
8.4.3. Memory Access Determinism
TERN enforces synchronization orders, which should
make memory access orders more deterministic. We
quantified this effect over Apache and PBZip2. Specif-
ically, we instrumented Apache with LLVM to trace ac-
cesses to global variables and the heap, a crude approxi-
mation of shared memory. We ran Apache with TERN to
serve five HTTP requests and collected a trace of mem-
ory accesses. We then repeated this experiment 20 times
to collect 20 traces, and computed the average pairwise
edit distance [52]. We then measured the same edit dis-
tance for Apache in nondeterministic execution mode
and compared the two. We did the same comparison
for PBZip2 with a decompression workload of 2MB. Ta-
ble 8 shows the result. For Apache, runs with TERN were
7.97 times more deterministic than those without. For
PBZip2, TERN was 2.33 times more deterministic, but
the memory trace had only 1,234 accesses on average.
9 Related Work
Deterministic Execution TERN differs from existing
DMT systems [13, 22, 41] by making threads stable, i.e.,
repeating familiar behaviors across different inputs. An-
other difference is that TERN reduces timing nondeter-
minism for server programs through windowing.
The closest system to TERN in this category is
Kendo [41], a software-only DMT system that also en-
forces synchronization orders instead of memory ac-
cess orders for efficiency. COREDET [13] is another
software-only DMT system that enforces deterministic
memory access orders. Both systems are based on log-
ical clocks and have been shown to work on scien-
tific benchmarks, such as SPLASH2. The authors of
COREDET have noted that a small modification to the
original program leads to a much different COREDET-
instrumented program, which the idea of schedule mem-
oization may address. COREDET is a software imple-
mentation (with extensions) of DMP [22], a hardware
DMT system .
Grace [14] proposes a novel approach to making C and
C++ programs with fork-join parallelism behave like se-
quential programs. It runs each thread within a process
and commits memory writes atomically and determin-
istically. It detects memory access conflicts efficiently
using hardware page protection. Grace has been shown
to perform and scale well on Phoenix benchmarks [45]
and a Cilk [15] benchmark. Unlike Grace, TERN aims to
make general multithreaded programs, not just fork-join
programs, deterministic and stable.
Deterministic Replay Deterministic replay [9, 23, 24,
27, 31, 33, 34, 40, 44, 50, 51] aims to replay the exact
recorded executions, whereas TERN “replays” memoized
schedules on different inputs. Some recent deterministic
replay systems include Scribe, which tracks page owner-
ship to enforce deterministic memory access [34]; Capo,
which defines a novel software-hardware interface and
a set of abstractions for efficient replay [40]; PRES and
ODR, which systematically search for a complete exe-
cution based on a partial one [9, 44]; and SMP-ReVirt,
which uses clever page protection trick for recording the
order of conflicting memory accesses [24].
Concurrency Errors The complexity in developing
multithreaded programs has led to many concurrency er-
rors [39]. A significant number of them are not data
races, but atomicity and order errors [39], which can be
deterministically reproduced or avoided using only syn-
chronization orders.
Much work exists on concurrency error detection [25,
37, 38, 47, 55, 56], diagnosis [42, 43, 48], and correc-
tion [32, 53]. TERN aims to make the executions of
multithreaded programs deterministic and stable, and is
complementary to existing work on concurrency errors.
Specifically, TERN can use existing work to detect and
fix the errors in the schedules it selects. Moreover, even
for programs free of concurrency errors, TERN still pro-
vides value by making their behaviors repeatable.
Symbolic Execution The combination of symbolic and
concrete executions has been a hot research topic. Re-
searchers have built scalable and effective symbolic ex-
ecution systems to detect errors [16—18, 20, 28-30, 49,
54], block malicious inputs [21], and preserve privacy in
error reports [19]. Compared to these systems, TERN ap-
plies symbolic execution to a new domain: tracking input
constraints to reuse schedules.
10 Conclusion
We have presented TERN, the first DMT system that
makes general multithreaded programs stable by repeat-
ing the same schedules on different inputs. TERN does
so using schedule memoization: if a schedule is shown to
work on an input, TERN memoizes the schedule; if a sim-
ilar input arrives later, TERN simply reuses the memo-
ized schedule. TERN is also the first DMT system to mit-
igate input timing nondeterminism for server programs.
Our TERN implementation runs on Linux. It requires
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) = 219
220
no new hardware, no modifications to the underlying OS
or synchronization library, and only a few lines of mod-
ifications to the multithreaded programs. We evaluated
TERN on a diverse set of real programs, including two
server programs, one desktop program, and 11 scien-
tific programs. Our results show that TERN is easy to
use, makes programs more deterministic and stable, and
has reasonable overhead. TERN is the first DMT sys-
tem shown to work on applications as large, complex,
and nondeterministic as MySQL and Apache. It demon-
strates that DMT has the potential to be deployed today.
Acknowledgement
We thank Cristian Cadar, John Gallagher, Michael
Kester, Emery Berger (our shepherd), and the anony-
mous reviewers for their tremendous feedback and com-
ments, which have substantially improved the content
and presentation of this paper. We thank Shan Lu for pro-
viding some of the concurrency errors used in our evalu-
ation. We thank Jane-Ellen Long for time management.
Michael Kester wrote the script for replaying the HTTP
trace from the Columbia CS website.
This work was supported by the National Science
Foundation (NSF) through Contract CNS-1012633 and
CNS-0905246 and the Air Force Research Labora-
tory (AFRL) through Contract FA8650-10-C-7024 and
FA8750-10-2-0253. Opinions, findings, conclusions,
and recommendations expressed in this material are
those of the authors and do not necessarily reflect the
views of the US Government.
References
[1] ab - Apache HTTP server benchmarking tool. http://
httpd.apache.org/docs/2.2/programs/ab.html.
[2] Artici Terns - Wikipedia.
wiki/Arctic_Tern.
http://en.wikipedia.org/
[3] The LLVM Compiler Framework. http://1llvm.org.
[4] MySQL Database. http: //www.mysql.com/.
[5] Parallel BZIP2 (PBZIP2).
pbzip2/.
[6] Stanford Parallel Applications for Shared Memory (SPLASH).
http://www-flash.stanford.edu/apps/SPLASH/.
http://compression.ca/
[7] SysBench: a system performance benchmark. http://
sysbench.sourceforge.net.
[8] A. V. Aho, M. S. Lam, R. Sethi, and J. D. Ullman. Compil-
ers: Principles, Techniques, and Tools (2nd Edition). Addison-
Wesley, 2006.
[9] G. Altekar and I. Stoica. ODR: output-deterministic replay for
multicore debugging. In Proceedings of the 22nd ACM Sympo-
sium on Operating Systems Principles (SOSP ’09), pages 193-
206, 2009.
Apache Web Server. http: //www.apache.org.
T. Ball and J. R. Larus. Branch prediction for free. In PLDI
°93: Proceedings of the ACM SIGPLAN 1993 conference on Pro-
gramming language design and implementation, pages 300-313,
1993.
[10]
[11]
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
[12]
[13]
[14]
[15]
[16]
[17]
[18]
[19]
[20]
[21]
[22]
[23]
[24]
[25]
[26]
[27]
T. Ball and J. R. Larus. Efficient path profiling. In MJCRO 29:
Proceedings of the 29th annual ACM/IEEE international sympo-
sium on Microarchitecture, pages 46-57, 1996.
T. Bergan, O. Anderson, J. Devietti, L. Ceze, and D. Grossman.
Coredet: a compiler and runtime system for deterministic mul-
tithreaded execution. In Fifteenth International Conference on
Architecture Support for Programming Languages and Operat-
ing Systems (ASPLOS °10’), pages 53-64, 2010.
E. Berger, T. Yang, T. Liu, D. Krishnan, and A. Novark. Grace:
Safe and efficient concurrent programming. In Conference on
Object-Oriented Programming Systems, Languages, and Appli-
cations (OOPSLA) 2009, 2009.
R. D. Blumofe, C. F. Joerg, B. C. Kuszmaul, C. E. Leiserson,
K. H. Randall, and Y. Zhou. Cilk: an efficient multithreaded
runtime system. J. Parallel Distrib. Comput., 37(1):55-69, 1996.
C. Cadar, V. Ganesh, P. M. Pawlowski, D. L. Dill, and D. R.
Engler. EXE: automatically generating inputs of death. In Pro-
ceedings of the 13th ACM conference on Computer and commu-
nications security (CCS ’06), pages 322-335, Oct.—Nov. 2006.
C. Cadar, D. Dunbar, and D. Engler. KLEE: Unassisted and auto-
matic generation of high-coverage tests for complex systems pro-
grams. In Proceedings of the Eighth Symposium on Operating
Systems Design and Implementation (OSDI ’08), pages 209-224,
Dec. 2008.
G. Candea, S. Bucur, and C. Zamfir. Automated software test-
ing as a service. In Proceedings of the 1st Symposium on Cloud
Computing (SOCC ’10), 2010.
M. Castro, M. Costa, and J.-P. Martin. Better bug reporting with
better privacy. In ASPLOS XIII: Proceedings of the 13th inter-
national conference on Architectural support for programming
languages and operating systems, pages 319-328, 2008.
V. Chipounov, V. Georgescu, C. Zamfir, and G. Candea. Selective
Symbolic Execution. In Fifth Workshop on Hot Topics in System
Dependability (HotDep ’09), 2009.
M. Costa, M. Castro, L. Zhou, L. Zhang, and M. Peinado.
Bouncer: securing software by blocking bad input. In SOSP ’07:
Proceedings of twenty-first ACM SIGOPS symposium on Operat-
ing systems principles, pages 117-130, 2007.
J. Devietti, B. Lucia, L. Ceze, and M. Oskin. Dmp: determinis-
tic shared memory multiprocessing. In Fourteenth International
Conference on Architecture Support for Programming Languages
and Operating Systems (ASPLOS ’09), pages 85-96, 2009.
G. Dunlap, S. T. King, S. Cinar, M. Basrat, and P. Chen. ReVirt:
enabling intrusion analysis through virtual-machine logging and
replay. In Proceedings of the Fifth Symposium on Operating Sys-
tems Design and Implementation (OSDI ’02), Dec. 2002.
G. W. Dunlap, D. G. Lucchetti, M. A. Fetterman, and P. M. Chen.
Execution replay of multiprocessor virtual machines. In Proceed-
ings of the 4th International Conference on Virtual Execution En-
vironments (VEE ’08), pages 121-130, 2008.
D. Engler and K. Ashcraft. Racerx: Effective, static detection of
race conditions and deadlocks. In Proceedings of the 19th ACM
Symposium on Operating Systems Principles (SOSP ’03), pages
237-252, Oct. 2003.
J. A. Fisher and S. M. Freudenberger. Predicting conditional
branch directions from previous runs of a program. In ASPLOS-
V: Proceedings of the fifth international conference on Architec-
tural support for programming languages and operating systems,
pages 85-95, 1992.
D. Geels, G. Altekarz, P. Maniatis, T. Roscoey, and I. Stoicaz.
Friday: Global comprehension for distributed replay. In Proceed-
USENIX Association
[28]
[29]
[30]
[31]
[32]
[33]
[34]
[35]
[36]
[37]
[38]
[39]
[40]
[41]
USENIX Association
ings of the Fourth Symposium on Networked Systems Design and
Implementation (NSDI ’07), Apr. 2007.
P. Godefroid, N. Klarlund, and K. Sen. Dart: Directed automated
random testing. In Proceedings of the ACM SIGPLAN 2005 Con-
ference on Programming Language Design and Implementation
(PLDI ’05), pages 213-223, June 2005.
P. Godefroid, A. Kiezun, and M. Y. Levin. Grammar-based white-
box fuzzing. In PLDI ’08: Proceedings of the 2008 ACM SIG-
PLAN conference on Programming language design and imple-
mentation, pages 206-215, 2008.
P. Godefroid, M. Levin, and D. Molnar. Automated whitebox
fuzz testing. In NDSS ’08: Proceedings of 15th Network and
Distributed System Security Symposium, Feb. 2008.
Z. Guo, X. Wang, J. Tang, X. Liu, Z. Xu, M. Wu, M. F. Kaashoek,
and Z. Zhang. R2: An application-level kernel for record and
replay. In Proceedings of the Eighth Symposium on Operating
Systems Design and Implementation (OSDI ’08), pages 193-208,
Dec. 2008.
H. Jula, D. Tralamazza, Z. Cristian, and C. George. Deadlock im-
munity: Enabling systems to defend against deadlocks. In Pro-
ceedings of the Eighth Symposium on Operating Systems Design
and Implementation (OSDI ’08), pages 295-308, Dec. 2008.
R. Konuru, H. Srinivasan, and J.-D. Choi. Deterministic replay
of distributed Java applications. In Proceedings of the 14th In-
ternational Symposium on Parallel and Distributed Processing
(IPDPS ’00), pages 219-228, May 2000.
O. Laadan, N. Viennot, and J. Nieh. Transparent, lightweight
application execution replay on commodity multiprocessor op-
erating systems. In Proceedings of the 2010 ACM SIGMETRICS
Conference on Measurement and Modeling of Computer Systems,
2010.
E. A. Lee. The problem with threads. Computer, 39(5):33-42,
2006.
S. Lu, Z. Li, F Qin, L. Tan, P. Zhou, and Y. Zhou. Bugbench:
Benchmarks for evaluating bug detection tools. In Proceedings of
the first Workshop on the Evaluation of Software Defect Detection
Tools (BUGS ’05), June 2005.
S. Lu, J. Tucek, F. Qin, and Y. Zhou. Avio: Detecting atomic-
ity violations via access interleaving invariants. In Twelfth Inter-
national Conference on Architecture Support for Programming
Languages and Operating Systems (ASPLOS ’06), pages 37-48,
Oct. 2006.
S. Lu, S. Park, C. Hu, X. Ma, W. Jiang, Z. Li, R. A. Popa, and
Y. Zhou. Muvi: automatically inferring multi-variable access cor-
relations and detecting related semantic and concurrency bugs.
SIGOPS Oper. Syst. Rev., 41(6):103-116, 2007.
S. Lu, S. Park, E. Seo, and Y. Zhou. Learning from mistakes:
a comprehensive study on real world concurrency bug character-
istics. In ASPLOS XIII: Proceedings of the 13th international
conference on Architectural support for programming languages
and operating systems, pages 329-339, 2008.
P. Montesinos, M. Hicks, S. T. King, and J. Torrellas. Capo:
a software-hardware interface for practical deterministic multi-
processor replay. In Fourteenth International Conference on Ar-
chitecture Support for Programming Languages and Operating
Systems (ASPLOS ’09), pages 73-84, 2009.
M. Olszewski, J. Ansel, and S. Amarasinghe. Kendo: efficient
deterministic multithreading in software. In Fourteenth Inter-
national Conference on Architecture Support for Programming
Languages and Operating Systems (ASPLOS ’09), pages 97-108,
2009.
[42]
[43]
[44]
[45]
[46]
[47]
[48]
[49]
[50]
[51]
[52]
[53]
[54]
[55]
[56]
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
C.-S. Park and K. Sen. Randomized active atomicity violation de-
tection in concurrent programs. In Proceedings of the 16th ACM
SIGSOFT International Symposium on Foundations of Software
Engineering (SIGSOFT ’08/FSE-16), pages 135-145, Nov. 2008.
S. Park, S. Lu, and Y. Zhou. CTrigger: exposing atomicity vio-
lation bugs from their hiding places. In Fourteenth International
Conference on Architecture Support for Programming Languages
and Operating Systems (ASPLOS ’09), pages 25-36, Mar. 2009.
S. Park, Y. Zhou, W. Xiong, Z. Yin, R. Kaushik, K. H. Lee, and
S. Lu. PRES: probabilistic replay with execution sketching on
multiprocessors. In Proceedings of the 22nd ACM Symposium on
Operating Systems Principles (SOSP ’09), pages 177-192, 2009.
C. Ranger, R. Raghuraman, A. Penmetsa, G. Bradski, and
C. Kozyrakis. Evaluating mapreduce for multi-core and multi-
processor systems. In HPCA ’07: Proceedings of the 2007 IEEE
13th International Symposium on High Performance Computer
Architecture, pages 13-24, 2007.
M. Ronsse and K. De Bosschere. Recplay: a fully integrated
practical record/replay system. ACM Trans. Comput. Syst., 17
(2):133-152, 1999.
S. Savage, M. Burrows, G. Nelson, P. Sobalvarro, and T. E. An-
derson. Eraser: A dynamic data race detector for multithreaded
programming. ACM Transactions on Computer Systems, pages
391-411, Nov. 1997.
K. Sen. Race directed random testing of concurrent programs.
In Proceedings of the ACM SIGPLAN 2008 Conference on Pro-
gramming Language Design and Implementation (PLDI ’08),
pages 11-21, June 2008.
K. Sen, D. Marinov, and G. Agha. CUTE: A concolic unit testing
engine for C. In Proceedings of the 10th European Software En-
gineering Conference held jointly with the 13th ACM SIGSOFT
International Symposium on Foundations of Software Engineer-
ing (ESEC/FSE-13), pages 263-272, Sept. 2005.
S. M. Srinivasan, S. Kandula, C. R. Andrews, and Y. Zhou. Flash-
back: A lightweight extension for rollback and deterministic re-
play for software debugging. In Proceedings of the USENIX
Annual Technical Conference (USENIX ’04), pages 29-44, June
2004.
VMWare Virtual Lab Automation.
com/solutions/vla/.
http://www.vmware.
R. A. Wagner and M. J. Fischer. The string-to-string correction
problem. J. ACM, 21(1):168-173, 1974.
Y. Wang, T. Kelly, M. Kudlur, S. Lafortune, and S. Mahlke.
Gadara: Dynamic deadlock avoidance for multithreaded pro-
grams. In Proceedings of the Eighth Symposium on Operating
Systems Design and Implementation (OSDI ’08), pages 281-294,
Dec. 2008.
J. Yang, C. Sar, P. Twohey, C. Cadar, and D. Engler. Automat-
ically generating malicious disks using symbolic execution. In
Proceedings of the 2006 IEEE Symposium on Security and Pri-
vacy (SP 06), pages 243-257, May 2006.
Y. Yu, T. Rodeheffer, and W. Chen. RaceTrack: efficient detec-
tion of data race conditions via adaptive tracking. In SOSP ’05:
Proceedings of the twentieth ACM symposium on Operating sys-
tems principles, pages 221-234, 2005.
W. Zhang, C. Sun, and S. Lu. Conmem: detecting severe con-
currency bugs through an effect-oriented approach. In Fifteenth
International Conference on Architecture Support for Program-
ming Languages and Operating Systems (ASPLOS ’10’), pages
179-192, 2010.
221
USENIX Association
Enabling Configuration-Independent Automation by Non-Expert Users
Nate Kushman
Massachusetts Institute of Technology
Abstract
The Internet has allowed collaboration on an unprece-
dented scale. Wikipedia, Luis Von Ahn’s ESP game, and
reCAPTCHA have proven that tasks typically performed
by expensive in-house or outsourced teams can instead be
delegated to the mass of Internet computer users. These
success stories show the opportunity for crowd-sourcing
other tasks, such as allowing computer users to help each
other answer questions like “How do I make my com-
puter do X?”. The current approach to crowd-sourcing IT
tasks, however, limits users to text descriptions of task so-
lutions, which is both ineffective and frustrating. We pro-
pose instead, to allow the mass of Internet users to help
each other answer how-to computer questions by actually
performing the task rather than documenting its solution.
This paper presents KarDo, a system that takes as input
traces of low-level user actions that perform a task on in-
dividual computers, and produces an automated solution
to the task that works on a wide variety of computer con-
figurations. Our core contributions are machine learning
and static analysis algorithms that infer state and action
dependencies without requiring any modifications to the
operating system or applications.
1 Introduction
Computer systems are becoming increasingly complex.
As a result, users regularly encounter tasks that they do
not know how to perform such as configuring their home
router, removing a virus, or creating an email account.
Many users do not have technical support, and hence
their first, and often only, resort is a web search. Such
searches, however, often lead to a disparate set of user fo-
rums written in ambiguous language. They rarely make
clear which user configurations are covered by a par-
ticular solution; descriptions of different problems over-
lap; and many documents contain conjectured solutions
that may not work. The net result is that users spend
Dina Katabi
Massachusetts Institute of Technology
hours manually working through large collections of doc-
uments to try solutions that often fail to help them per-
form their task.
What a typical user really wants is a system that auto-
matically performs the task for him, taking into account
his machine configuration and global preferences, and
asking the user only for information that cannot be au-
tomatically pulled from his computer. Today, however,
automation requires experts to program scripts. This pro-
cess is slow and expensive and hence unlikely to scale to
the majority of tasks that users perform. For instance,
a recent automation project at Microsoft succeeded in
scripting only about 150 of the hundreds of thousands
of knowledge-base articles in a period of 6 months [10].
This paper introduces KarDo, a system that enables
the mass of Internet users to automate computer tasks.
KarDo aims to build a database of automated solutions
for computer tasks. The key characteristic of KarDo is
that a user contributes to this database simply by perform-
ing the task. For lay users this means interacting with
the graphical user interface, which manifests itself as a
stream of windowing events (i.e., keypresses and mouse
clicks). KarDo records the windowing events as the user
performs the task. It then merges multiple such traces
to produce a canonical solution for the task which en-
codes the various steps necessary to perform the task on
different configurations and for different users. A user
who comes across a task he does not know how to per-
form searches the KarDo database for a matching solu-
tion. The user can either use the solution as a tutorial that
walks him through how to perform the task step by step,
or ask KarDo to automatically perform the task for him.
The key challenge in automating computer tasks based
on windowing events is that events recorded on one ma-
chine may not work on another machine with a differ-
ent configuration. To address this problem, a system
needs to understand the dependencies between the sys-
tem state and the windowing events. While the system
could track these dependencies explicitly by modifying
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) = 223
224
(a) Abstract ion (b) Generalization
LX +
LO +
sy os & Per task state- @
SS ¢ sf a
LF OSS modifying graph ®@
I | Ny Global navigation
& #& € € graph for all tasks
s ¥ < xe
¢ S& e é
e s
>
+
Fa
& os of e Ros
SEF fF SS SC
Figure 1: Mlustration of KarDo’s three-stage design.
the OS and potentially applications [18], such an ap-
proach presents a high deployment barrier and is hard to
use for tasks that involve multiple machines (e.g., config-
uring a wireless router). KarDo therefore adopts an ap-
proach that implicitly infers system state dependencies,
and does not require modifying the OS or applications.
In particular, KarDo builds a model that maps window-
ing events to abstract actions that capture impact on sys-
tem state: UPDATE and COMMIT actions, which actu-
ally modify system state, and NAVIGATE actions, which
simply open or close windows but do not modify system
state. KarDo performs this mapping automatically us-
ing machine learning. It then runs a set of static analysis
algorithms on these sequences of abstract actions to pro-
duce a canonical solution which can perform the task on
various different configurations. The system operates in
3 stages, described below and shown in Fig. 1.
(a) Abstraction. KarDo first captures the context around
each windowing event (e.g, the associated application,
window, widget efc.) using the accessibility interface,
which was originally developed for visually impaired
users and is supported by modern operating systems [8,
5]. KarDo then extracts from the context a per event fea-
ture vector, which it uses in a machine learning algorithm
to map the event to the corresponding abstract action.
Fig. 1(a) illustrates this operation.
(b) Generalization. KarDo then performs static analysis
on the abstract actions in each recorded trace to elimi-
nate irrelevant actions that do not affect the final system
state. Once it has the relevant actions for each task, it
proceeds to generalize them to deal with diverse config-
urations. Since navigation actions do not update state,
KarDo can learn the many diverse ways to navigate the
GUI from totally unrelated tasks, and therefore builds a
global navigation graph across all tasks. In contrast, for
state-modifying actions (i.e., UPDATES and COMMITS),
KarDo uses differences across recordings of the same
task to learn the different sequences of state-modifying
actions that perform the task on various configurations,
and represents this knowledge as a per task directed graph
parameterized by configuration. Fig. 1(b) illustrates the
generalization stage.
(c) Replay. In order to perform the task in a specific
environment, KarDo walks down the graph of state-
modifying actions trying to find a branch where all the
actions involve applications (i.e. Thunderbird, Firefox,
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
etc.) that exist on the machine. Once it finds such a
branch, it proceeds to execute the actions along it. It
moves from one state modifying action to the next by
leveraging the global navigation graph to find a path from
one of the active desktop widgets to the widget corre-
sponding to the next state-modifying action. Fig. 1(c)
illustrates the replay stage.
We built a prototype of KarDo as a thin client con-
nected to a cloud-based service. We evaluate KarDo on
57 computer tasks drawn from the Microsoft Help web-
site [9] and the eHow [4] websites which together include
more than 1000 actions and include tasks like configuring
a firewall, web proxy, and email. We generate a pool of
20 diversely configured virtual machines which we sep-
arate into 10 training VMs and 10 test VMs. For each
task, two users performed the task on two randomly cho-
sen VMs from the training set. We then attempt to per-
form the task on the 10 test VMs. Our results show that a
baseline that tries both user traces on each test VM, and
picks whichever works better, succeeds in only 18% of
the cases. In contrast, KarDo succeeds on 84% of the
500+ VM-task pairs. Thus, KarDo can automate com-
puter tasks across a wide variety of configurations with-
out modifying the OS or applications.
We also performed a user study on 5 different com-
puter tasks, to evaluate how well KarDo performs com-
pared to humans for the same set of tasks. Even with
detailed instructions from our lab website the students
failed to correctly complete the task in 20% of the cases.
In contrast, when given traces from all 12 users, KarDo
produced a correct canonical solution which played back
successfully on a variety of different machines.
2 Challenges
A system that aims to automate computer tasks based on
user executions and without instrumenting the OS or ap-
plications, needs to attend to multiple subtleties.
(a) Generalizing Navigation. Consider the task of con-
figuring a machine for access through remote desktop.
On Microsoft Windows, the first step is to enable remote
desktop on the local machine through the “System” dia-
log box which is accessed through the Control Panel. Au-
tomatically navigating to this dialog box can be difficult
however because the Control Panel can be configured in
three different ways. Novice users typically retain the de-
USENIX Association
USENIX Association
© Control Panel
File Edit View Favorites Tools Help
Deak - E- B PSearch (Folders FH]~
Address |@ Control Panel .
2 @ S$ ee ®@
Printers Regional Scanners Sched... Security Sounds
and Faxes andLa... andC.. Tasks Center and..
ae 8 @ 2
System Taskbar User Windows Windows Wireless
and St... Accounts CardSp... Firewall Netw...
(a) Classic View
® Control Panel
File Edit View Favorites Tools Help
> ©O- & Perch & folders
@ Corttrol Panel
Dre
(b) Category View
Figure 2: Diverse Configurations. To enable remote desktop one
must go to the “System” dialog box. Depending on the configuration
of the Control Panel, one can either directly click the “System” icon (a)
or must first navigate to “Performance and Maintenance” (b) then click
the “System” icon.
fault view which uses a category based naming scheme,
as in Fig. 2(a). Most advanced users however switch to
the “Classic View” which always shows all available con-
trols, as in Fig. 2(b). And, efficiency oriented users often
go as far as configuring the control panel so it appears as
an additional menu off of the start menu. All three paths
however lead to the same “System” dialog box where one
can turn on remote desktop. The challenge is to produce
a canonical GUI solution that performs the task on ma-
chines with any of these configurations even when the
recorded traces for this task show only one of the possi-
ble configurations.
(b) Filtering Mistakes and Irrelevant Actions. KarDo
needs a mechanism to detect mistakes and eliminate ir-
relevant actions that are not necessary for the task. For
example, while performing a task, the user may acciden-
tally open some program that turns out to not be relevant
for the task. If this mistake is included in the final so-
lution, however, it will require the playback machine to
have this irrelevant program installed in order for KarDo
to automatically perform the task. It is important to re-
move mistakes like this to prevent the need for the user
to rerecord a second “clean” trace, thus allowing users to
generate usable recordings as part of their everyday work.
(c) Parameterizing Replay. After enabling remote desk-
top on his local machine, the user needs to configure the
router to allow through the incoming remote desktop con-
nections and direct them to the right machine. KarDo can
easily automate a task like this, since it is done through
a web-browser interface to the router, which provides the
same accessibility information as all other GUI applica-
tions. The challenge arises, however, because one user
may have a static IP address while another has a dy-
namic IP address, or worse, one user might have a DLink
router, while another has a Netgear. Different steps are
required to perform this task if the user has a static IP
address vs. a dynamic IP address. Similarly, different
routers present different web-based configuration inter-
faces, so users with different routers need to perform dif-
ferent GUI actions to perform this task. KarDo needs
to retain each of these paths in the final canonical so-
lution, and parametrize them such that the appropriate
path can be chosen during playback. The challenge is
to distinguish these configuration based differences from
mistakes and irrelevant actions so that the former can be
retained while the later are removed.
(d) User-Specific Entries. Some tasks require a user to
enter his name, password, or other user-specific entries.
KarDo can easily avoid recording passwords by recog-
nizing that the GUI naturally obfuscates them, provid-
ing a simple heuristic to identify them. However, KarDo
also needs to recognize all other entries that are user spe-
cific and distinguish them from entries that differ across
traces because they are mistakes or configuration-based
differences. It is critical to distinguish user specific en-
tries from mistakes and configuration differences because
KarDo should ask the user to input something like his
username, while it should automatically discover which
path to follow for different router manufacturers.
3 KarDo Overview
KarDo is a system that enables end users to automate
computer tasks without programming, and does not re-
quire modifications to the OS or applications. It has two
components, a client that runs on the user machine to
do recording and playback, and a server that contains a
database of solutions.
When a user performs a task that he thinks might be
useful to others, he asks the KarDo client to record his
windowing events while he performs the task. If the user
cannot, or does not want to perform the task on his ma-
chine, he can perform the task remotely on a virtual ma-
chine running on the KarDo server, while KarDo records
his windowing events. In either case, when the user is
done, the client uploads the resulting windowing event
trace to the KarDo server. The server asks the user for
a task name and description. It uses this information to
search its database for similar tasks and asks the user if
his task matches any of those. This ensures that all traces
for the same task are matched together.
When a user encounters a task he does not know how
to perform, he searches the KarDo database for a solu-
tion. KarDo’s search algorithm has access to not only the
information that a normal text search would have, such
as the task’s name and description, the steps of the task,
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) = 225
226
and the text of each relevant widget, but also system level
information like which programs are installed, and which
GUI actions he has taken recently. As a result, we believe
that task search with KarDo can be much more effective
than standard text searching is today. However, effective
search represents a research paper on its own, and so we
leave the search algorithm details to future work.
The user can either use the solution as a tutorial that
will walk him through how to perform the task step by
step, or allow the solution to automatically perform the
task for him. It is important to recognize however that
KarDo’s solutions are intended to be best-effort. Even a
highly evolved system will not be able to automate cor-
rectly all of the time. Thus, KarDo takes a Microsoft Vir-
tual Shadow Service snapshot before automatically per-
forming any task, and immediately rolls back if the user
does not confirm that the task was successfully performed
(as discussed in §8, however, we leave the security as-
pects of this problem to future work).
The next three sections detail the three steps for trans-
forming a set of traces recorded on one set of machines
into a solution which allows automated replay on any
other machine. §4 covers how to record the windowing
events and map them to abstract actions that highlight
how each action affects the system state. §5 then de-
scribes how to merge together multiple such sequences
of abstract actions to create a generalized solution for any
configuration. Finally, 86 discusses how replay utilizes
the generalized solution and the state of the playback ma-
chine to determine the exact set of playback steps appro-
priate for that machine.
4 Windowing Events to Abstract Actions
The first phase of generating a canonical solution from
a set of traces is to transform a windowing event trace
into a sequence of abstract actions, since the generaliza-
tion phase, discussed in §5 works over abstract actions.
Performing this abstraction requires first converting the
trace to a sequence of raw GUI actions by associating
GUI context information with each windowing event, and
then mapping raw GUI actions to abstract actions using a
machine learning classifier.
4.1 Capturing GUI Context
A low-level windowing event contains only the specific
key pressed, or the mouse button click along with the
screen location. Effectively mapping these low-level
events to abstract actions requires additional information
about the GUI context in which that event took place such
as which GUI widget is at the screen location where the
mouse was clicked. KarDo gathers this information using
the Microsoft Active Accessibility (MSAA) interface [8].
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
Developed to enable accessibility aids for users with im-
paired vision, the accessibility interface has been built
into all versions of the Windows platform since Windows
98 and is now widely supported [8]. Apple’s OS X al-
ready provides a similar accessibility framework [7], and
the Linux community is working to standardize a single
accessibility interface as well [5]. The accessibility in-
terface provides information about all of the visible GUI
widgets, including their type (button, list box, etc.), their
text name, and their current value, among other charac-
teristics. It also provides a naming hierarchy of each wid-
get which we use to uniquely name the widget. KarDo
uses this context information to transform each window-
ing event to a raw GUI action performed on a particular
widget. An example of such a raw GUI action is a left
click on the OK button in the Advanced tab in the “In-
ternet E-mail Setting” window.
4.2 Abstract Model
KarDo uses an abstract model for GUI actions. This
model captures the impact that each action has on the
underlying system state. We do not claim that our model
captures all possible applications and tasks, however, it
does capture common tasks (e.g., installation, configura-
tion changes, network configurations, e-mail, web tasks)
performed on typical Windows applications (e.g., MS Of-
fice, IE, Thunderbird, FireFox) as shown from the 57
evaluation tasks in Table 3. As discussed in 812, it also
can be extended if important non-compliant tasks or ap-
plications arise.
In the abstract model all actions are performed on wid-
gets. A widget could be a text box, a button, etc. There
are three types of abstract actions in KarDo’s model:
UPDATE Actions: These actions create a pending change
to the system state. Examples of UPDATE actions include
editing the state of an existing widget, such as typing into
a text box or checking a check-box, and adding or remov-
ing entries in the system state, e.g., an operation which
adds or removes an item from a list-box.
COMMIT/ABORT Actions: These actions cause pending
changes made by UPDATE actions to be written back into
the system state. An example of a COMMIT action is
pressing the OK button, which commits all changes to all
widgets in the corresponding window. An ABORT action
is the opposite: it aborts any pending state changes in the
corresponding window, e.g., pressing a Cancel button.
NAVIGATE Actions: These change the set of currently
visible widgets. NAVIGATE actions include opening a di-
alog box, moving from one tab to another, or going to the
next step of a wizard by pressing the Next button.
Note that a single raw GUI action may be converted
into multiple abstract actions. For example, pressing the
USENIX Association
USENIX Association
Raw GUI Actions Abstract Actions
Click Open Dialog
Check Check Box
Click OK
Navigate to Dialog;
Update (Dialog, Widget, ,Check)
Commit (Dialo:
_| Navigate to Main —
idget,)
Click Open Dialog
UnCheck Check Box
Click OK
Navigate to Dialog,
Update (Dialog, Widget, ,Uncheck)
Commit (Dialog,, Widget,)
Click Open Dialog
Check Check Box
Click Cancel
get, ,Check)
Abort (Dialog,, Widget,)
Navigate to Main
Figure 3: A simplified illustration mapping raw GUI action to the
corresponding abstract actions.
OK button both commits the pending states in the corre-
sponding window and navigates to a new view.
Fig. 3 illustrates a simple sequence of raw GUI actions
and the corresponding abstract actions. Here a user clicks
to open a dialog box, clicks to check a check box, and
then clicks OK. He then realizes that he made a mistake
and opens the dialog again to uncheck the check box. Fi-
nally, he opens the dialog one last time, rechecks the box,
but reconsiders his change and hits the Cancel button.
The corresponding sequence of abstract actions shows
that the user navigated thrice to the dialog box, updated
the check box, committed or aborted the UPDATE, and
navigated again to the main window. However, the ab-
stract model allows us to reason that the first UPDATE
and the corresponding NAVIGATE and COMMIT actions
are overwritten by the later UPDATE and hence are re-
dundant and can be eliminated. Similarly, since the last
UPDATE and associated ABORT do not update the state,
they too can be eliminated. In 85.1, we describe KarDo’s
static analysis algorithm for filtering out such mistakes.
4.3. Mapping to Abstract Actions
KarDo has to label the raw GUI actions returned by
the accessibility interface as UPDATE, COMMIT, and/or
NAVIGATE. It does not attempt to explicitly classify
ABORT actions because KarDo’s algorithms implicitly
treat the lack of a COMMIT action as an ABORT action
as explained in 85.1. Further, a given action can have
multiple different abstract action labels, or not have any
label at all. KarDo performs the labeling as follows.
To label an action as a NAVIGATE action, KarDo uses
the simple metric of observing whether new widgets be-
come available before the next raw action. Specifically,
KarDo’s recordings contain information about not only
the current window, but all other windows on the screen.
Thus, if an action either changes the available set of wid-
gets in the current window, or opens another window,
Widget name (typically the text on the widget)
: Widget role (i.e., button, edit box, etc.
Widget/ 8 : ( 3 )
: Does the widget contain a password?
Window : :
Pestirke Is the widget updatable (i.e., check box, etc.)?
. Is the widget in a menu with checked menu items?
Does the window contain an updatable widget?
Did the action cause a window to close?
Response Did the action cause a window to open?
To Action Did the action generate an HTTP POST?
Features Did the action cause the view to change?
Did the action cause the view state to change?
Action type (right mouse click, keypress, etc.)
: Keys pressed (the resulting strin
Action ye P ( os te 8) ”
Does the keypress contain an “enter”?
Features 3 :
Does the keypress contain alpha numeric keys?
Is this the last use of the widget?
Table 1: SVM Classifier Features. This table shows the list of fea-
tures used by the SVM classifier to determine which actions are UP-
DATE and COMMIT actions. All features are encoded as binary features
with multi-element features (such as widget name) encoded as a set of
binary features with one feature for each possible value.
then KarDo labels that action as a NAVIGATE action.!
Labeling an action as a COMMIT or UPDATE action is
not as straightforward. There are cases where this label-
ing is fairly simple; for example, typing in a text box or
checking a check box is clearly an UPDATE action. But to
handle the more complex cases, KarDo approaches this
problem the same way a user would, by taking advantage
of the visual features on the screen. For example, a typ-
ical feature of a COMMIT action, is that it is associated
with a user clicking a button whose text comes from a
small vocabulary of words like {OK, Finish, Yes}.
KarDo does this labeling using a machine learning
(ML) classifier. Specifically, an ML classifier for a given
class takes as input a set of data points, each of which is
associated with a vector of features and produces as out-
put a label for each data point indicating whether or not
it belongs to that class. It does this labeling by learning a
set of weights which indicate which features, and which
combinations of features, are likely to produce a posi-
tive data point, and which are likely to produce a nega-
tive data point. KarDo uses a supervised classifier, which
does this learning based on a small set of training data.
KarDo uses two separate classifiers, one for COM-
MITS and one for UPDATES. These classifiers take as
input a data point for each user action (i.e., each mouse
click or keypress), and label them as UPDATES and COM-
MITS respectively. 7 Table 1 shows the features used by
KarDo’s classifiers to determine the labels. Features such
as widget name, and widget role cannot be used directly
by the classifiers however, because classifiers only work
with numerical features. Thus, KarDo handles features
'KarDo will also label a window close as a NAVIGATE action in
cases like a modal dialog box, where the user cannot interact with the
underlying window again until the dialog box is closed.
2Note that since a given action is fed to both classifiers it can be
classified as both an UPDATE and a COMMIT to account for actions
like clicking the “Clear Internet Cache” button which both update the
state and immediately commit that update.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 710) = 227
228
like these, which are character strings, using the same
technique as the Natural Language Processing commu-
nity. Specifically, it adds a new binary feature for each
observed string, i.e., is the the widget name “OK”, is
the widget name “Close”, etc. This creates a relatively
large number of features for each action which can cause
a problem called overfitting, where the classifier works
well only on the training data set, and it does not gen-
eralize to new data. To handle this large number of fea-
tures, KarDo uses a type of classifier called a Support
Vector Machine (SVM) which is robust to large num-
bers of features because it uses a technique called margin
maximization. KarDo trains the SVM classifier using a
set of training data from one set of traces, while all testing
is done using a distinct set of traces.
5 Generalization
Generalization starts with multiple abstract action traces
which perform the same task on different configurations
and transforms them into a single canonical solution that
performs the task on all configurations. KarDo performs
this step by separating how it handles NAVIGATE actions
from how it handles state modifying actions, i.e. UP-
DATES and COMMITS. Specifically, it first prunes out all
NAVIGATE actions from each trace (and all unlabeled ac-
tions), leaving only the state modifying actions. It then
follows a three step process to generate a canonical solu-
tion: (1) it runs a static analysis algorithm on each pruned
trace that removes all the mistakes and irrelevant UP-
DATES; (2) these simplified traces are merged together
to create a single canonical trace which is parameterized
by user-specific environment; and (3) the NAVIGATE ac-
tions from all traces for all tasks are utilized to create a
global navigation graph which is used to do navigation
during playback. The rest of this section describes these
three steps in detail.
5.1 Filtering Mistakes
The first step of generalization is to filter out mistakes
from each trace. To understand the goal of filtering
out mistakes, consider the example in Fig. 3, where the
user opens the dialog box multiple times, changing the
value of a given widget each time. In this example, the
first check box UPDATE is overwritten by the second,
while the third is never committed. Thus both of these
UPDATES are unnecessary, and they should be removed
along with the opening and closing of the dialog box as-
sociated with them. Their removal is important for two
reasons. First, if a user chooses to read the text version of
a solution, or to have KarDo walk him through the task,
then such mistakes will be confusing to the user. Sec-
ond, if not removed, mistakes like this can be confused as
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
Pass 1: Remove Unnecessary Updates Pass 2: Remove Unnecessary Commits
Navigate to Dialog, Navigate to Dialog,
iadetialog Mideuec
Commit (Dialog, Widget,)
Navigate to Main
Navigate to Main
Navigate to Dialog,
pdate (Dialog, Widget
Commit (Dialog, Widget,)
Navigate to Main
Navigate to Dialog,
Update (Dialog, Widget,)
commit (Dialog,, Widget,) |
Navigate to Main
Navigate to Dialog, Navigate to Dialog,
Abort (Dialog, Widget,)
Navigate to Main
Figure 4: A Two-Pass Algorithm to Remove Mistakes.
Abort (Dialog, Widget,)
Navigate to Main
user-specific or environment-specific actions and hence
limit our ability to generalize.
The naive approach to identifying mistakes would
compare multiple GUI traces from users who performed
the same task, and consider differing actions as mistakes.
Unfortunately, such an approach will also eliminate nec-
essary actions which differ due to differences in users’
personal information (e.g., printer name) or their work-
ing environment (e.g., different wireless routers).
In contrast, the key idea in KarDo is to recognize that
the difference between unnecessary actions and environ-
ment specific actions is that unnecessary actions do not
affect the final system state, and GUIs are merely a way
of accessing this system state. So KarDo tracks the state
represented by each widget and keeps only actions that
affect the final state of the system. It does this using the
following two-pass static analysis algorithm that resem-
bles the algorithms used in various log recovery systems
to determine the final set of committed UPDATES.
Pass 1 - Filtering Out Unnecessary UPDATES: The first
pass removes all UPDATES on a particular widget except
the last UPDATE which actually gets committed. Specif-
ically, consider again our example from Fig. 3 where a
user opens a given dialog box, and modifies a widget
three times. We can see that KarDo needs to recognize
that the second UPDATE overwrote the first UPDATE, ren-
dering the first unnecessary. However, it cannot blindly
take the last UPDATE, because the final UPDATE was
aborted. Thus KarDo needs to keep the final committed
UPDATE for each widget. It does this by walking back-
wards through the trace maintaining both a list of out-
standing COMMITS, and a list of widgets for which it’s
already seen a committed UPDATE. As it walks back-
wards, it removes both UPDATES without outstanding
COMMITS and UPDATES for which it’s already seen a
committed UPDATE on that same widget.
Pass 2: Filtering Out Unnecessary COMMITS: The
second pass removes COMMITS with no associated UP-
DATES. It does this by walking forwards through the
trace maintaining a set of pending UPDATES. When it
reaches an UPDATE, it adds the affected widget to the
USENIX Association
USENIX Association
pending set. When it reaches a COMMIT, if there are any
widget(s) associated with this COMMIT in the pending
set, it removes them from the pending set, otherwise it
removes the COMMIT from the trace.
One may fear that there are cases in which having the
system go through an intermediate state is necessary even
if that state is eventually overwritten. For example, if
the task involves disabling a webserver, updating some
configuration that can only be modified when the web-
server is disabled and then re-enabling the webserver,
it would be incorrect to remove the disabling and re-
enabling of the webserver. While in theory such prob-
lems could arise, we find that in practice they do not
arise. This is because actions like enabling and disabling
a webserver typically look to KarDo like independent
UPDATES which do not reverse each other, since one
may require clicking the “disable” button while the other
requires clicking the “enable” button. This causes the
mistake removal algorithm to be somewhat conservative,
which is the appropriate bias since it’s worse to remove
a required action than to leave a couple of unnecessary
actions.
5.2 Parametrization
The second step of generalization is to parameterize the
traces. Specifically, now that we have removed mis-
takes and navigation actions, the remaining differences
between traces of the same task are either user specific
actions (e.g. user name), or machine configuration dif-
ferences (static IP vs. dynamic IP) which change the set
of necessary UPDATE or COMMIT actions. To integrate
these differences into a canonical trace that works on all
configurations KarDo parametrizes the traces as follows:
(a) Parametrize UPDATES. The values associated with
some UPDATE actions, such as usernames and pass-
words, are inherently user specific and cannot be auto-
mated. KarDo identifies these cases by recognizing when
two different traces of the same task update the same wid-
get with different values. To handle these kinds of Up-
DATES, KarDo parses all traces of a task to find all unique
values that were given to each widget via UPDATE ac-
tions that were subsequently committed. Based on these
values the associated UPDATE actions are marked as ei-
ther AutoEnter if the associated widget is assigned the
same value in all traces of that task, or UserEnter if the
associated widget is assigned a different value in each
trace. On play back, AutoEnter UPDATES are performed
automatically, while KarDo will stop play back and ask
the user for UserEnter actions. Note that if the widget is
assigned to a few different values, many of which occur
in multiple traces (e.g., a printer name), KarDo will as-
sign it PossibleAutoEnter, and on play back let the user
select among values previously entered by multiple dif-
ferent users or enter a new value.
(b) Parameterized Paths. All of the remaining differ-
ences between traces now stem from configuration dif-
ferences in the underlying machine, which necessitate a
different set of UPDATES or COMMITS in order to per-
form the same task. To handle this type of difference,
KarDo recognizes that when a user’s actions in two dif-
ferent traces differ because of the underlying machine
configuration, the same action will generate two different
resulting views. For example, consider the task of setting
up remote desktop. Different traces may have used dif-
ferent routers, which require different sets of actions to
configure the router. Since the routers are configured via
a web browser, opening a web browser and navigating to
the default IP address for router setup, http://192.168.1.1,
will take the user to a different view depending on which
router the user has. KarDo takes advantage of this to rec-
ognize that if the DLink screen appears, then it must fol-
low the actions from the trace for the DLink router, and
similarly for the other router brands.
Thus, KarDo builds a per-task state-modifying graph
and automatically generates a separate execution branch
with the branch point parameterized by how the GUI
reacts, e.g., which router configuration screen appears.
This ensures that even when differences in the underly-
ing system create the need for different sets of UPDATES
and COMMITS, KarDo can still automatically execute the
solution without needing help from the user. If the traces
actually perform different actions even though the under-
lying system reacts exactly the same way, then these are
typically mistakes, which would be removed by our fil-
tering algorithm above. If differences still exist after fil-
tering, this typically represents two ways of performing
the same step in the task, i.e. downloading a file using IE
vs. Firefox. Thus KarDo retains both possible paths in
the canonical solution and if both are available on a given
playback machine, then KarDo will choose the path that
is the most common among the different traces.
5.3 Building a Global Navigation Graph
Real world machines expose high configuration diversity.
This diversity stems from basic system level configura-
tion like which programs a user puts on their desktop
and which they put in their Start Menu, to per application
configuration like whether a user enables a particular tool
bar in Microsoft Word, or whether they configure their
default view in Outlook to be e-mail view or calendar
view. All of these configuration differences affect how
one can reach a particular widget to perform a necessary
UPDATE or COMMIT. KarDo handles this diversity with
only a few traces for each task by leveraging that multiple
tasks may touch the same widget, and building a single
general navigation graph using traces for all tasks.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) = 229
230
Run
Dialog
Box
Figure 5: Illustration of the Navigation Graph. A simplified illus-
tration showing a few ways to reach the IE window. The actual graph
is per widget and includes many more edges.
Building such a general navigation graph is relatively
straightforward. KarDo marks each NAVIGATE action as
an enabler to all of the widgets that it makes available.
KarDo then adds a link to the navigation graph from the
widget this NAVIGATE action interacted with (e.g., the
icon or button that is clicked), to the widgets it made
available, and associates this NAVIGATE action with that
edge. Fig. 5 presents a simplified illustration of a por-
tion of the navigation graph. It shows that one can run IE
from the desktop, the Start menu, or the Run dialog box.
6 Replay
The replay process takes a solution constructed using the
process described in the preceding sections, and produces
the low-level window events to perform a task on a par-
ticular machine. At each step, this process utilizes the
full navigation graph, the per-task state-modifying de-
pendency graph, and the current GUI context.
During replay, KarDo walks down the task’s state-
modifying dependency graph. As described in 85.2, this
graph is parameterized by GUI context. Thus, KarDo
utilizes the current GUI context and the installed applica-
tions to determine the path to follow at any branch point.
At each step, KarDo needs to ensure that the next state-
modifying action is enabled. To enable a given UPDATE/
COMMIT action, KarDo finds the shortest directed path
in the navigation graph between the widget required for
the UPDATE/COMMIT action, and any widget that is cur-
rently available on the screen. KarDo finds this path by
working backwards in the navigation graph. Specifically,
it first checks to see if the necessary widget is already
available. If not, it looks in the navigation graph for all
incoming edges to the necessary widget, and checks to
see if any of the widgets associated with those edges are
available. If not, it checks the incoming edges to those
widgets, etc. It continues this process until either it finds
a widget which is already available on the screen, or there
are no more incoming edges to parse.
Once KarDo’s navigation algorithm finds a relevant
widget in the navigation graph which is currently avail-
able on the screen, it performs the associated action. If
the expected next widget in the graph appears, KarDo
follows the path through the navigation graph until the
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
widget associated with the necessary UPDATE/COMMIT
action becomes available. If at any point, the expected
widget that the edge leads to does not appear, however,
KarDo marks that navigation edge as unusable, and again
performs the above search process. * It continues this
process until either it succeeds in making the necessary
UPDATE/COMMIT widget appear on the screen, or it has
exhausted all possibilities and has no paths left in the nav-
igation graph between widgets currently on the screen
and the next necessary UPDATE/COMMIT widget.
Finally, each abstract action, whether NAVIGATE or
state-modifying, is mapped to a low-level windowing
event by utilizing the accessibility interface similar to the
way it is used during recording.
7 Solution Validation
When a user uploads a solution for a task, KarDo allows
the user to provide a solution-check. To do so, the user
performs the steps necessary to confirm the task has been
completed correctly and highlights the GUI widget that
indicates success. For example, to check an IPv6 config-
uration, the user can go to ipv6.google.com and highlight
the Google search button. As with standard tasks, KarDo
will map the trace to abstract actions, clean it from ir-
relevant actions, etc. Such solution-checks allow KarDo
to confirm that its canonical solution for a task works on
all configurations by playing the solution followed by its
solution-check on a set of VMs with diverse configura-
tions, and checking that in each VM the highlighted GUI
widget has the same state as in the solution-check.
8 Security
Ensuring that users cannot insert malicious actions into
KarDo’s solutions is an important topic that represents a
research paper on its own. We do not attempt to tackle
that problem in this paper. To handle non-malicious mis-
takes, however, KarDo takes a Microsoft Virtual Shadow
Service snapshot before automatically performing a task
and rolls back if the user is unhappy with the results.
9 Implementation
The KarDo implementation has three components: a
client for doing the recording and the playback, a server
to act as the solution repository, and a virtual machine
infrastructure for remote recording and solution testing.
9.1 Client
Our current KarDo client is built on Microsoft Win-
dows as a browser plugin. The user interface runs in the
3It caches the searched subgraphs to speed up any later searches.
USENIX Association
USENIX Association
browser and is built using standard HTML and Javascript
which communicate with the plugin to provide all KarDo
functionality. The plugin is written natively in C++. As
discussed in §4.1, the plugin uses the OS Accessibility
API to do the recording and playback.
The main implementation challenge in the client is to
ensure that the GUI context of each mouse click and key-
press can be recorded before the GUI changes as a re-
sult of the user action, i.e. before the window closes as
a result of clicking the “OK” button. KarDo achieves
the timely recording of the GUI context by utilizing the
Windows Hooks API, which allows registration of a call-
back function to be called immediately before keypress
and/or mouse click messages are passed to the applica-
tion. The challenge is that such a callback function needs
to be extremely fast, otherwise the UI feels sluggish to
the user [17]. Calls to MSAA to get the GUI context are
very slow, however, for two reasons: (1) they use the Mi-
crosoft Component Object Model (COM)* interface to
marshal and unmarshal arguments for each function call,
and (2) MSAA requires a separate call for each attribute
of each widget on the screen (e.g., a widget name or role)
often resulting in thousands of COM function calls per
window.
We use two main techniques to maintain acceptable
recording performance. First, we implement the callback
function in a shared library so that it can run in-process
with the application receiving the click/keypress. This
significantly improves performance since it avoids the
overhead of COM IPC for each function call. Second,
instead of recording the GUI context of every window
on the screen with every user input, we record only the
full context of the window receiving the user input, and
for all other windows we record only high level informa-
tion such as the window handle, and window title. As we
show in §10.4, this significantly improves performance
when the user has many other windows open.
9.2 Solution Repository Server
The solution server provides a central location for upload,
download and storage of all solutions. In our current im-
plementation, all solution merging also happens on the
server. We implement the solution server on Linux us-
ing a standard Apache/Tomcat server backed by a Post-
gres database. All solutions are stored on disk, with all
meta-data stored in the database. When the client fin-
ishes recording a trace, KarDo immediately asks the user
if he would like to upload the trace. Upon confirmation,
the client uploads the trace to the server, and the server
searches its existing database for solutions with similar
sets of steps, and asks the user to confirm if his trace
matches any of these. The server also provides a web in-
44 binary interface used for inter-process communication
terface listing all solutions. When a user finds a task they
would like automatically performed, they click the Play
button which calls into the client browser plugin to down-
load that solution from the server and start playback.
9.3. Virtual Machine Infrastructure
The VM infrastructure is used for two purposes: 1) to en-
able users to record a solution for a task which they either
cannot or do not want to perform on their own machine;
and 2) to perform solution validation as discussed in §7.°
KarDo’s VM infrastructure is build on top of Kernel-
based Virtual Machine (KVM)[6]. Its design is based on
Golden Master (GM) VM images, which are generic ma-
chine images that have been configured to expose a cer-
tain dimension of configuration diversity, or make avail-
able a certain set of tasks. For example, some GMs are
configured with static IP addresses, while others have dy-
namic IP addresses, and some have Outlook as the default
mail client, while others have Thunderbird. The infras-
tructure can then quickly bring up a running snapshot of
any GM by taking advantage of KVM’s copy-on-write
disks and its memory snapshotting support.
10 Evaluation
We evaluate KarDo on 57 computer tasks which together
include more than 1000 actions and are drawn from the
Microsoft Help website [9] and the eHow [4] website.
We chose these tasks by randomly pulling articles from
the websites and then eliminating those which did not
describe an actual task (i.e. “What does Microsoft Ex-
change do?”), those which described hardware changes
(i.e. “How to add more RAM”), and those which required
software to which we did not already have a license. We
focused on common programs, e.g., Outlook, IE, and di-
versified the tasks to address Web, Email, Networking,
etc. The full list of tasks is shown in Table 3 and includes
tasks like configuring IPv6, defragmenting a hard drive,
and setting up remote desktop.
10.1 Handling Configuration Diversity
Our goal with KarDo is to handle the wide diversity of
ways in which users configure their machines. Measur-
ing KarDo’s performance on a small number of actual
user machines is not representative of the wide diversity
of configurations, however, since many users leave the
default option for most configuration settings. To capture
this wide diversity, we generate a pool of 20 virtual ma-
chines whose configurations differ along the following
axes: differences of installed applications (e.g., Firefox
>We also used it to produce the evaluation results in §10.1.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI’10) = 231
232
100
80
60
40
20
%age of VMs Succeeding on Replay
(Ordered) Taskld
Figure 6: Success Rate on Diverse Configurations: For each
task on the x-axis the figure plots on the y-axis the percentage of test
VMs that succeeded in performing the task using a specific automation
scheme. For each scheme, the area under the curve refers to the success
rate taken over all task-VM pairs. KarDo-Two-Traces has a success rate
of 84%, whereas KarDo-One-Trace has a success rate of 64%. In con-
trast, Best-Trace, which tries both of the two traces and picks whichever
works better, has a success rate of only 18%, and Random-Trace, which
randomly chooses between the two traces, has a success rate of only
11%.
vs. IE, Thunderbird vs. Outlook), differences of per-
application configuration (e.g., different enabled tool and
menu bars), user-specific OS configuration (e.g., differ-
ent views of the control panel, different icons on the desk-
top), and different desktop states (e.g., different windows
or applications already opened). We apply each configu-
ration option to a random subset of the VMs. This results
in a set of machines with more configuration diversity
than normal, but which represent the kind of diversity of
configurations we would like to handle.
We separate this pool of VMs into 10 training and 10
test. We recruited a set of 6 different users to help us
record traces, including 2 non-expert users and 4 com-
puter science experts. For each of the 57 evaluation tasks,
two of the six users perform the task on two randomly
chosen VMs from the training set. We then try to replay
each task on the 10 test VMs. We compare four schemes:
e KarDo - Two Traces: We generate a canonical so-
lution by merging together the two traces for each
task, and we generate a navigation graph using all
of the traces from all tasks. We then use the KarDo
replay algorithm to playback the resulting solutions
on the test VMs.
e KarDo - One Trace: We randomly pick one of the
two traces and use it to generate a canonical solution
for that task. The navigation graph is generated from
that trace plus all traces for all other tasks (but not
the other trace for that same task).
e BaseLine - Best Trace: For each VM, we try di-
rectly playing both of the two recorded traces for
each task. If either trace succeeds then we report
success for that VM-task combination. This shows
how well a baseline system would perform with two
traces per task.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
e BaseLine - Random Trace: We randomly pick one
of the two traces and directly playback all of the
GUI actions in the original trace on the test VMs.
This represents how well a baseline system would
perform with only one trace per task.
Fig. 6 plots the success rate of these four schemes. It
shows that the Best-Trace approach succeeds on average
on only 18% of the VMs while the Random-Trace suc-
ceeds on just 11% of the test VMs. In contrast, KarDo
succeeds on 84% of the 500+ VM-task pairs when given
two traces, and on 64% when given only one trace. Thus,
KarDo enables non-programmers to automate computer
tasks across diverse configurations.
10.2 Understanding Baseline Errors
The Best-Trace and Random-Trace schemes are very
susceptible to configuration differences. Even a sin-
gle configuration difference can cause the Random-Trace
scheme to fail. The two traces considered by the Best-
Trace approach make it more robust to configuration dif-
ferences, but it still only works if the test VM looks very
similar to one of the VMs on which the recordings were
performed. Consider a case where one recording opened
Outlook from the desktop, and then accessed a menu item
to change some configuration, and the other recording
opened it from the Start Menu, and then used the tool bar
to change that configuration. Even in this simple case
where the two recordings see a large amount of diversity
between them, the Best-Trace algorithm cannot handle a
case where the tool bars are turned off, but Outlook is not
on the desktop, or a case where menus are turned off, but
Outlook is not in the Start Menu. More generally, even
if the test VM is a hybrid of the two VMs on which the
traces were recorded, the Best-Trace approach will fail.
This is because a hybrid configuration requires pulling
different parts from each of the traces which cannot be
done without KarDo’s technique of merging the traces
together. Thus, the Best-Trace approach requires an ex-
cessive number of examples to successfully play back on
diverse machines. Finally, we note that there are a num-
ber of tasks where the Best-Trace fails on all VMs. This
occurs when all test VMs are widely different from the
two VMs where the recordings were performed.
10.3. Understanding KarDo Errors
While KarDo successfully plays back in the vast major-
ity of the cases, it still fails to playback successfully on
16% of the VM-task pairs. There are three main causes
of these errors: classifier mistakes, incorrect navigation
steps, and missing navigation steps. Fig. 7 shows the
breakdown of these errors. Specifically it shows that
eliminating classification errors results in a 91% success
USENIX Association
USENIX Association
100
80
60
40
20
Oracle Classifier-Oracle Navigation
Oracle Classifier-Standard Navigation
KarDo (SVM Classifier-Standard Navigation)
%age of VMs Succeeding on Replay
0 10 20 30 40 50
(Ordered) Taskld
Figure 7: Cause of KarDo Errors: This figure shows the breakdown
of the KarDo playback errors by showing the playback success when
various parts of the KarDo algorithms are replaced by oracle versions.
Recall that the success rate is the area under the curve. Based on the
figure, replacing KarDo’s classifiers with oracle classifiers increases the
playback success rate from 84% to 91%. Additionally, eliminating all
mistakes in the navigation database by using an oracle for navigation
increases the playback success rate from 91% to 95%. The remaining
5% failure cases result from missing navigation steps that did not appear
in any of the input traces.
rate while eliminating incorrect navigation steps results
in a 95% success rate. We observe that the remaining 5%
of the errors result mostly from missing navigation steps.
The following discusses each of these in detail.
(a) ML Classification Errors: To evaluate our ML clas-
sifier, we manually labeled each of the actions performed
by the users for the 57 tasks as a COMMIT action, an UP-
DATE action, both or neither. We then split this labeled
data into half training and half test data. As described
in §4.3 we run two separate classifiers on the data, one
for UPDATE actions, and one for COMMIT actions. Since
KarDo’s generalization algorithm (from 85) retains only
COMMITS and UPDATES as necessary actions, false neg-
ative misclassifications will cause KarDo to skip one of
these necessary UPDATES or COMMITs during playback.
False positives on the other hand will cause unneces-
sary actions to be retained, requiring KarDo to attempt
to playback irrelevant actions which may be unavailable
ona test VM. We calculate the false positive rate for each
of the two classifiers as the percentage of actions in the
COMMIT/UPDATE class that should not be in it, and the
false negative rate as the percentage of actions not in the
COMMIT/UPDATE class but should be in it.
The resulting performance of the KarDo classifiers is
shown in Table 2. As we can see, the ML classifiers per-
form quite well even though classification mistakes ac-
count for almost half of the playback failures. Specifi-
cally, the COMMIT classifier has a false positive rate of
only 2% and a false negative rate of only 3%. The COM-
MIT classifier performs so well because COMMITS follow
very predictable patterns, i.e., they almost always occur
when a button is pressed, and very frequently cause the
associated window to close. The UPDATE classifier per-
|__|] False Positive Rate | False Negative Rate
Commins | __ 2% 3%
_Urpares | __ 6% 5%
Table 2: Performance of the COMMIT and UPDATE Classifiers.
forms slightly worse with a 6% false positive rate and a
5% false negative rate. The higher false positive rate for
UPDATES is caused by actions using widgets like combo
boxes and edit boxes which are typically used for UP-
DATES, but are sometimes used just for navigational pur-
poses. Occasionally when an action uses one of these
widgets only for navigation (i.e., it’s not an UPDATE),
KarDo will misclassify the action as an UPDATE action.
The higher false negative rate stems from actions which
are both UPDATES and COMMITS. These actions tend to
look much more like COMMITS than UPDATES and as a
result the COMMIT classifier typically correctly classifies
them, but the UPDATE classifier occasionally misclassi-
fies them, not realizing they are also UPDATES. One
such example is clicking the button to defragment your
hard drive, which looks very much like a COMMIT ac-
tion as it is a button click, and closes the associated win-
dow, but does not look very much like a typical UPDATE
action since button clicks usually do not update any sys-
tem state. In fact, if we test the UPDATE classifier after
removing actions that are both COMMITS and UPDATES
from the training and test sets the false negative rate drops
to 2% without increasing the false positive rate at all.
Note that a misclassification does not necessarily cause
an error in the resulting canonical trace. In particular,
only misclassifications that result in the eventual discard
of a necessary action produce erroneous task solutions.
For example, one may misclassify an action that is both
COMMIT and UPDATE as only COMMIT. Still, as long as
the mistake removal algorithm keeps this action as neces-
sary, the resulting solution will still perform the UPDATE.
To evaluate the effect of classification mistakes on the
final playback performance, we ran an “Oracle Classi-
fier’ version of KarDo where instead of using the output
from the ML classifier to determine whether an action
is an UPDATE or a COMMIT, we directly use the hand
generated labels so that all classifications are correct. As
shown in Fig. 7 this increases the playback success rate
by an additional 7%. More training data would help elim-
inate these mistakes.
(b) Incorrect Navigation Steps: The next cause of play-
back problems comes from limitations in the way we
currently generate the navigation graph. As discussed
in $4.3, KarDo assumes that navigation depends only on
the final action that made a widget visible. In a few cases,
however, navigation depends on other earlier actions in
the trace. A simple example of this is the “Run” dialog
box which allows a user to type in the name of a program
and then click “OK” to run it. In this case, the naviga-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) = 233
234
1800
1600
1400
1200
1000
800
600
400
200
Just ‘Main Window
Time (Millisecs)
0 5 10 15 20
Number of Windows
Figure 8: Real Time Window System Context Recording: The
figure shows that KarDo’s optimized recording, which limits recording
full context information to only the main window, has a response time
less than 100ms regardless of the number of windows. This is signifi-
cantly below the 200ms threshold at which users perceive the UI to be
sluggish. In contrast, recording the full context of all windows has a
response time that scales with the number of windows, eventually be-
coming very slow.
tion depends not only on clicking “OK”, but also on the
program name filled into the edit box.
To test the effect of incorrect navigation steps on the fi-
nal playback success, we hand labeled all such dependent
navigation actions. We then ran a “Oracle Navigation”
version of KarDo where each navigation step had the full
set of required actions associated with it. As shown in
Fig. 7 this increases the playback success by an additional
4%. These mistakes can be eliminated by the additional
classifier discussed in $12.
(c) Missing Navigation Steps: The final cause of play-
back problems stems from KarDo’s fairly limited view
of the GUI navigation landscape, due to the relatively
small number of input traces in our experiments. Specif-
ically, since many of the traces KarDo uses to generate
its solutions are performed by users that already know
how to perform a task, these traces rarely include navi-
gation information related to incorrect navigations. This
can cause playback to fail in the small fraction of cases
where KarDo navigates in a way that is not appropriate
for a given configuration and thus results in an error di-
alog box or some other GUI widget/window which was
not seen in any trace. In this case, to ensure that it does
not cause any problems, KarDo will immediately abort
playback and roll back the user’s machine to its original
state. These type of errors account for most of the re-
maining 5% of playback errors shown in Fig. 7, and can
be solved by more traces.
10.4 Feasibility Micro-Benchmarks
We want to ensure that KarDo’s design performs well
enough to be feasible in practice. To test this, we ran
three performance tests on a standard 2.4 GHz Intel
Core2 Duo desktop machine.
First, as discussed 89.1, context recording has to be
fast so that it does not cause the user to perceive the UI as
sluggish. Fig. 8 shows that even with many windows on
the screen, KarDo can grab the relevant windowing sys-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
14
~ 12
wn
2 10
8 8
2 6
e 4
Fo oo2
0
0 5 10
15 20 25 30 35 40 45 50
Number of Tasks Merged
Figure 9: Performance of Solution Merging: The figure graphs the
time that KarDo takes to merge a given number of traces, showing that
eae can scale to quickly merge a large number of traces for a given
tem context in well less than 100ms, and the overhead is
relatively constant regardless of the number of windows.
Since users only start to notice delay when it is greater
than 200ms [17], this additional delay should be accept-
able to users. In contrast a scheme which records the
context of all windows reaches an unacceptable delay of
more than | second with even just 15 windows open.
Next, we check the performance of solution merging.
Fig. 9 shows that merging up to 50 traces takes only 15
seconds, and it takes less than a second to merge 5 traces.
This result shows that KarDo can easily scale to merging
a large number of traces for each task.
Finally, KarDo’s playback is relatively fast. For the 57
tasks in Table 3, playing a KarDo solution takes on av-
erage 52 seconds with a standard deviation of 9 seconds.
The maximum replay time was 125 seconds, which was
mostly spent waiting for the virus scanner to finish.
10.5 Working with Users
We evaluate KarDo’s ability to improve on the status quo
of using text instructions to perform computer tasks. We
asked 12 CS students to perform 5 computer tasks within
1 hour, based on instructions from our lab website. We
also used KarDo to automate each task by merging the
students’ traces into a single canonical solution.
We find three important results. First, as shown in
Fig. 10(a), even with detailed instructions, the students
fail to correctly complete the tasks in 20% of the cases.
In contrast, KarDo always succeeded in generating a so-
lution that automated the task on all 12 user machines.
Second, as shown in Fig. 10(b), even when the stu-
dents did complete the tasks they performed on average
84% more GUI actions than necessary, and sometimes
more than three times the necessary number of actions.
KarDo’s automation removes most of these irrelevant ac-
tions, performing only 11% more actions than necessary.
Third, as shown in Fig. 10(c), KarDo reduced the per-
task required number of times the user had to interact
with the machine from 25 to 2 times, on average. This
reduction is because KarDo requires manual entry only
for user-specific inputs, and automates everything else.
USENIX Association
USENIX Association
IMAP Double-Sided Active Virus Scan Fix FireFox
Configuration Printing Directory Certificate
KarDo Yes Yes Yes Yes Yes
User 1 Yes Yes Yes Yes Yes
User 2 Yes No Yes Yes Yes
User 3 Yes Yes Yes Yes Yes
User 4 Yes No No Yes Yes
User 5 No No Yes Yes Yes
User 6 No Yes No Yes Yes
User 7 Yes Yes Yes Yes Yes
User 8 Yes No Yes Yes Yes
User 9 Yes No Yes Yes Yes
User 10 No No Yes Yes Yes
User 11 Yes Yes Yes Yes Yes
User 12 Yes Yes Yes Yes Yes
(a) Task successes and failures.
200 m User Average m KarDo
%age of irrelevant actions
b
So
o
oO
IMAP DS Print AD Vscan Cert
(b) Percentage irrelevant actions performed by users and KarDo
40 m Manual =m KarDo
Fe 30
<x
g 20
2
10
» | a
IMAP DS Print AD Vscan Cert
(c) User manual inputs with and without KarDo.
Figure 10: Working with Users: The figures shows that (a) KarDo
performs the task correctly, even when many users fail, (b) KarDo fil-
ters most irrelevant actions, and (c) with KarDo users need to manually
perform very few steps, typically only those which require user-specific
information.
These results show that KarDo can help users reduce the
time and effort spent on IT tasks.
11 Related Work
While there are many tools to help automate computer
tasks, most either do not support recording and must
be scripted by programmers (e.g., AutoIt [2] and Auto-
HotKey [1]), or allow recording only by relying on appli-
cation specific APIs and thus cannot be used to automate
generic computer tasks (e.g., macros, Doc Wizards [14]).
Apple’s Automator [3], Sikuli [13] and AutoBash [18]
are the only exceptions as far as we know. However, nei-
ther Automator nor Sikuli can automatically produce a
canonical GUI solution that works on different machine
configurations. AutoBash covers only tasks which are
entirely contained on the local machine, which is increas-
ingly infrequent with today’s networked computer sys-
tems. Additionally, it requires modifying the kernel to
track dependencies across applications and then taking
diffs of the affected files. Such kernel modifications are a
deployment barrier, and file diffs are ineffective on binary
file formats.
Some tools support recording and check pointing, such
as DejaView [16], but they do not actually playback a
task, instead only returning to a checkpointed state.
Lastly, there are tools that leverage shared information
across a large user population [21, 20, 15, 19, 12, 11].
Strider [21] and PeerPressure [20] diagnose configura-
tion problems by comparing entries in Windows registry
on the affected machine against their values on a healthy
machine or their default values in the population. FTN
addresses the privacy problem in sharing configuration
state by resorting to social networks [15]. [19] and [12]
track kernel calls similar to AutoBash to determine prob-
lem signatures and their solutions. NetPrints [11] collects
examples of good and bad network configurations, builds
a decision tree, and determines the set of configuration
changes needed to change a configuration from bad to
good. All of these tools compare potentially problematic
state information against a healthy state to address com-
puter problems and failures. KarDo focuses on a com-
plementary issue where the existing machine state maybe
perfectly functional but the user wants to perform a new
task. KarDo addresses such how-to tasks by working at
the GUI level, which allows it to handle any general task
the user can perform.
12 Addressing KarDo’s Limitations
While our system represents a first step towards provid-
ing a system for automating a task by doing it, our cur-
rent implementation has multiple limitations we expect
to explore in future work. First, our model of labeling
all actions as COMMITS, UPDATES and NAVIGATE ac-
tions is not exhaustive. Specifically, it does not cover
tasks which simply show something on the screen. For
example, a task like “Find my IP Address” will look to
KarDo like it does nothing, and so all actions will be re-
moved. This can be addressed by extending the model.
Second, as discussed in 810.1, it does not handle tasks
containing complex navigation actions. For example if
navigation requires typing the name of a program in an
edit box and then clicking “Run” then KarDo will only
click the “Run” button. This can be solved using an addi-
tional classifier to detect these dependent navigation ac-
tions. Finally, KarDo requires unnecessary manual steps
when entering the same user specific information across
many tasks. For example, a user will have to manually
enter his Google username every time he wants to run any
task that accesses Google services.To handle this, we’d
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) = 235
236
like to build a profile for each user which will remember
previous inputs by a user and reuse them across tasks.
13. Concluding Remarks
This paper presents a system for enabling automation of
computer tasks, by recording traces of low-level user ac-
tions, and then generalizing these traces for playback on
other machine configurations through the use of machine
learning and static analysis. We show that automated
tasks produced by our system work on 84% of config-
urations, while baseline automation techniques work on
only 18% of configurations.
This paper has focused on use of our system for build-
ing an on-line repository of automated IT tasks which
would include both local configuration and setup as well
as remote tasks such as configuring a wireless router. We
note, however, that our system is useful for many other
applications as well, including replacing IT knowledge-
bases, automated software testing, and even use by expert
users as an easy way to automate repetitive tasks.
Acknowledgments
We'd like to thank Steve Bauer and Neil Van Dyke for
their help implementing an early version of the system,
and Micah Brodsky and Martin Rinard for help with the
mistake removal algorithm. Also, we greatly appreciate
Hariharan Rahul’s help editing an early draft of this pa-
per, and Sam Perli and Nabeel Ahmed’s help generating
early results. Lastly we’d like to thank Regina Barzilay,
S.R.K. Branavan, James Cowling, Evan Jones, Ramesh
Chandra, Jue Wang, Carlo Curino, Lewis Girod and our
shepherd Michael Isard for their feedback on the paper.
This work was supported by NSF grant ITS-0835652.
References
[1] AutoHotkey. http://www.autohotkey.com/.
[2] AutoIt, a freeware Windows automation language.
http://www.autoitscript.com/.
[3] Automator. http://developer.apple.com/macosx/automator.-html.
[4] eHow. http://www.ehow.com.
[5] IAcessibility2. http://www. linuxfoundation.org/en/Accessibility/-
TAccessible2.
[6] KVM. http://www.linux-kvm.org.
[7] Mac OS X Accessibility Framework . http://developer.apple-
Sending/
Receiving
Viewing
Junkmail
Security
Contacts/
Calendar
RSS
Feeds
Other
Excel
Word
Browser
Proxy
Security
and
Privacy
IPv6
DNS
Routing
Utilities
User
Interface
Settings
Other
E-mail
Turn off E-mail Read Receipts (54, 27)
Automatically forward e-mail to another address (35, 30)
Restore the unread mail folder (16, 8)
Highlight all messages sent only to me (31, 24)
Change an e-mail filtering rule (18, 19)
Add an e-mail filter rule (46, 26)
Make the recipient column visible in the Inbox (27, 11)
Order e-mail message by sender (19, 73)
Create an Outlook Search Folder (12, 12)
Turn on threaded message viewing in Outlook (16, 9)
Mark all messages as read (44, 48)
Automatically empty deleted items folder (22, 24)
Empty junk e-mail folder (9, 9)
Turn off Junk e-mail filtering (22, 14)
Consider people e-mailed to be safe senders (19, 25)
Send an e-mail with a receipt request (20, 12)
File Outlook contacts by last name (25, 13)
Set Outlook to start in Calendar mode (15, 23)
Add a new RSS feed (14, 15)
Change the Name of an RSS feed (12, 23)
Turn off Outlook Desktop Alerts (24, 35)
Reduce the size of a .pst file (26, 39)
Turn off notification sound (22, 66)
Switch calendar view to 24-hour clock (20, 14)
Office Applications
Delete a worksheet in Excel (8, 8)
Turn on AutoSave in Excel (33, 111)
Disable add-ins in Word (25, 23)
Web
Install Firefox (23, 21)
Manually Configure IE SSL Proxy (61, 83)
Set Default Http Proxy (7, 7)
Networking
Enable firewall exceptions (9, 9)
Enable Windows firewall (6, 6)
Disable Windows firewall notifications (8, 9)
Disable Windows firewall (6, 9)
Disable IPv6 to IPv4 tunnel (8, 7)
Show the current IPv4 routing table (10, 17)
Show the current IPv6 routing table (13, 10)
Use OpenDNS (44, 38)
Stop caching DNS replies (6, 9)
Use Google’s Public DNS servers (32, 32)
Use DNS server from DHCP (22, 22)
Configure system to pick routes based on link speed (22, 17)
Set routing interface metric (18, 19)
System
Analyze hard drive for errors (7, 13)
Defragment hard drive (10, 13)
Enable Automatic Updates (7, 6)
Set Up Remote Desktop (12, 10)
Hide the Outlook icon in the System tray (21, 18)
Change to Classic UI (15, 13)
Delete an Item from the Task Bar (13, 9)
Change desktop background color (35, 26)
Enable Accessibility Options (20, 20)
Auto-Hide the Taskbar (52, 41)
Change date to Long Format (33, 19)
Set Visual Effects for Performance (13, 13)
Set Outlook as default E-mail program (26, 15)
Enable Password on Screen Saver and Resume (22, 29)
[8
[9
[10
(11)
[12]
[13]
[14]
.com/documentation/Accessibility/Conceptual/AccessibilityMac-
OSX/AccessibilityMacOSX.pdf.
Microsoft Active Accessibility.
Microsoft-Active_Accessibility.
Microsoft Help. http://windows.microsoft.com/en-
us/windows/help.
Security Garden Blog. http://securitygarden.blogspot.com/2009/-
04/microsoft-fix-it-gadget.html.
B. Aggarwal, R. Bhagwan, T. Das, S. Eswaran, V. N. Padman-
abhan, and G. M. Voelker. Netprints: Diagnosing home network
misconfigurations using shared knowledge. In NSDI, 2009.
M. Attariyan and J. Flinn. Using causality to diagnose configura-
tion bugs. USENIX, 2008.
T.-H. Chang, T. Yeh, and R. C. Miller. Gui testing using computer
vision. In CHI, 2010.
L. D. B. et. al. DocWizards: A System For Authoring Follow-me
Documentation Wizards. In UIST, 2005.
http://en. wikipedia.org/wiki/-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
Table 3: 57 tasks used to evaluate KarDo. Each task is listed with the
number of actions performed in each of the two traces.
[15] Q. Huang, H. Wang, and N. Borisov. Privacy-Preserving Friends
TroubleShooting Network. In NDSS, 2005.
O. Laadan, R. A. Baratto, D. B. Phung, S. Potter, and J. Nieh.
Dejaview: A personal virtual computer recorder. In SOSP, 2007.
Olsen. Developing User Interfaces. Morgan Kaufmann, 1998.
Y. Su, M.A., and J. F. Autobash: improving configuration man-
agement with operating system causality analysis. SOSP, 2007.
Y.-Y. Su and J. Flinn. Automatically generating predicates and
solutions for configuration troubleshooting. USENIX, 2009.
H. J. Wang, J.P., Y.C., R.Z., and Y.-M. Wang. Automatic miscon-
figuration troubleshooting with peerpressure. In OSDI, 2004.
Y.-M. Wang and et. al. Strider: A black-box, state-based approach
to change and configuration management and support. In LISA,
2003.
[16]
[17]
[18]
[19]
[20]
[21]
USENIX Association
USENIX Association
Automating configuration troubleshooting with dynamic
information flow analysis
Mona Attariyan and Jason Flinn
University of Michigan
Abstract
Software misconfigurations are time-consuming and
enormously frustrating to troubleshoot. In this paper, we
show that dynamic information flow analysis helps solve
these problems by pinpointing the root cause of config-
uration errors. We have built a tool called ConfAid that
instruments application binaries to monitor the causal
dependencies introduced through control and data flow
as the program executes — ConfAid uses these depen-
dencies to link the erroneous behavior to specific to-
kens in configuration files. Our results using ConfAid to
solve misconfigurations in OpenSSH, Apache, and Post-
fix show that ConfAid identifies the source of the miscon-
figuration as the first or second most likely root cause for
18 out of 18 real-world configuration errors and for 55
out of 60 randomly generated errors. ConfAid runs in
only a few minutes, making it an attractive alternative to
manual debugging.
1 Introduction
Complex software systems are difficult to configure and
manage. When problems inevitably arise, operators
spend considerable time troubleshooting those problems
by identifying root causes and correcting them. The cost
of troubleshooting is substantial. Technical support con-
tributes 17% of the total cost of ownership of today’s
desktop computers [24], and troubleshooting misconfig-
urations is a large part of technical support. For informa-
tion systems, administrative expenses, made up almost
entirely of people costs, represent 60-80% of the total
cost of ownership [16]. Even for casual computer users,
troubleshooting is often enormously frustrating.
In this paper, we show that system support for dynamic
information flow analysis can substantially simplify and
reduce the human effort needed to troubleshoot software
systems. We focus specifically on configuration errors,
in which the application code is correct, but the software
has been installed, configured, or updated incorrectly so
that it does not behave as desired. For instance, a mistake
in a configuration file may lead software to crash, assert,
or simply produce erroneous output.
Why address misconfigurations specifically? Empiri-
cal evidence exists that misconfigurations are often the
dominant cause of problems in deployed systems. For
example, Gray [20] attributed 42% of system outages to
administration, while software, hardware, and environ-
ment failures account for 25%, 18%, and 14% of failures,
respectively. Murphy and Gent [31] note that the per-
centage of failures attributable to system management is
increasing over time, and that management failures have
come to dominate the combination of software and hard-
ware failures. Other studies have shown that configura-
tion errors are the largest category of operator mistakes.
Oppenheimer et al. [35] studied three commercial Inter-
net services and found that more than 50% of the opera-
tor mistakes that led to service unavailability were mis-
configurations. Nagaraja et al. [33] found that software
misconfiguration was the most common type of operator
mistake, accounting for more than half of all mistakes.
Other studies have shown similar results [7, 8, 23]. Fur-
ther, while fault tolerance techniques such as modular
redundancy [30] or Byzantine fault tolerance [10] can
mask software and hardware faults, they do not prevent
human error such as an operator who misconfigures all
replicas [20, 23].
Consider how users and administrators typically de-
bug configuration problems. Misconfigurations are often
exhibited by an application unexpectedly terminating or
producing erroneous output. While an ideal application
would always output a helpful error message when such
events occur, it is unfortunately the case that such mes-
sages are often cryptic, misleading, or even non-existent.
Thus, the person using the application must ask col-
leagues and search manuals, FAQs, and online forums to
find potential solutions to the problem. Troubleshooting
is a tedious, time-consuming process that can substan-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) = 237
238
tially increase the time to recover (TTR) from a failure.
To remedy this problem, we have developed a tool,
called ConfAid, that uses dynamic information flow
analysis to identify the likely root cause of a configu-
ration problem. When a user or administrator wishes to
troubleshoot a problem such as a crash or incorrect out-
put, she reproduces the problem while ConfAid modi-
fies the executed application binaries to track the causal
dependencies between configuration inputs and program
behavior. ConfAid produces an ordered list of the con-
figuration tokens most likely to have caused the exhibited
problem. While dynamic analysis takes a few minutes
for a complex application such as Apache, automated
troubleshooting is still considerably faster and less labor-
intensive than manual debugging or searching through
FAQs and online forums.
ConfAid dynamically tracks causality (i.e., informa-
tion flow) at a fine granularity, namely at the level of
instructions and bytes. While there is a large body of
work in the distributed systems community that tracks
causality to understand and troubleshoot program behav-
ior [2, 5, 6, 11, 12, 13], these prior systems essentially
treat application binaries as black boxes, understanding
causal relationships between processes by tracking net-
work messages and IPCs. Some gain more information
by inserting probes into applications to glean hints about
their activity. ConfAid, however, “opens up the black-
box” by examining the flow of causality within processes
as they execute. Further, since ConfAid tracks causality
using binary instrumentation [29], it does not require ap-
plication source code to find misconfigurations.
ConfAid restricts the scope of information flow anal-
ysis to only track values that depend on data read from
configuration files. ConfAid tracks dependencies intro-
duced by both data and control flow. If it determines that
altering a configuration parameter may change the ap-
plication’s control flow such that it avoids the problem
(and does not exhibit a different problem), it reports that
parameter as a possible root cause. It propagates depen-
dencies among multiple processes in a distributed system
by annotating IPCs and network communication.
Our results show that ConfAid identifies the correct
root causes of most configuration errors. We injected
18 real-world misconfigurations into OpenSSH, Apache,
and the Postfix email server. ConfAid identifies the cor-
rect root cause as the most likely source of the miscon-
figuration in 13 cases; for the remaining 5 bugs, it lists
the correct root cause as the second most likely option.
ConfAid analysis takes less than 3 minutes, making the
tool an attractive alternative to manual troubleshooting.
2 Design principles
We next briefly describe ConfAid’s design principles.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
2.1 Use white-box analysis
The genesis of ConfAid arose from AutoBash [37], our
prior work in configuration troubleshooting. AutoBash
tracks causality at process and file granularity in order
to diagnose configuration errors. It treats each process
as a black box, such that all outputs of the process are
considered to be dependent on all prior inputs. We found
AutoBash to be very successful in identifying the root
cause of problems, but the success was limited in that
AutoBash would often identify a complex configuration
file, such as Apache’s httpd.conf, as the source of an
error. When such files contain hundreds of options, the
root cause identification of the entire file is often too neb-
ulous to be of great use.
Our take-away lessons from AutoBash were: (1)
causality tracking is an effective tool for identifying root
causes, and (2) causality should be tracked at a finer
granularity than an entire process to troubleshoot appli-
cations with complex configuration files. These observa-
tions led us to use a white box approach in ConfAid that
tracks causality within each process at byte granularity.
The granularity of the root causes reported to the user
is also much finer. Instead of reporting the entire con-
figuration file as a root cause, ConfAid points its users
to specific tokens in the configuration file that it believes
to be in error. This approach narrows down root causes
considerably for programs like Apache.
2.2 Operate on application binaries
We next considered whether ConfAid should require ap-
plication source code for operation. While using source
code would make analysis easier, source code is unavail-
able for many important applications, which would limit
the applicability of our tool. Also, we felt it likely that
we would have to choose a subset of programming lan-
guages to support, which would also limit the number of
applications we could analyze.
For these reasons, we decided to design ConfAid to
not require source code; ConfAid instead operates on
program binaries. ConfAid uses Pin [29] to dynamically
insert instrumentation into binaries as applications run.
It also uses IDA Pro [22] to statically generate control
flow graphs from binaries.
2.3. Embrace imprecise analysis
Our final design decision was to embrace an imprecise
analysis of causality that relies on heuristics rather than
using a sound or complete analysis of information flow.
Using an early prototype of ConfAid, we found that for
any reasonably complex configuration problem, a strict
definition of causal dependencies led to our tool out-
putting almost all configuration values as the root cause
USENIX Association
USENIX Association
of the problem. Many registers and bytes in the address
space would come to depend on almost all configuration
values. Our prototype would identify the root cause as
only one of many possible causes.
Thus, our current version of ConfAid uses several
heuristics to limit the spread of causal dependencies. For
instance, ConfAid does not consider all dependencies
to be equal. It considers data flow dependencies to be
more likely to lead to the root cause than control flow
dependencies. It also considers control flow dependen-
cies introduced closer to the error exhibition to be more
likely to lead to the root cause than more distant ones. In
some cases, ConfAid’s heuristics can lead to false nega-
tives and false positives. However, our results show that
in most cases, they are quite effective in narrowing the
search for the root cause and reducing execution time.
3 Design and implementation
3.1 Overview: How ConfAid runs
ConfAid is designed to be used by system administra-
tors and end users when they encounter a suspected mis-
configuration that they do not know how to fix. Conf-
Aid is run offline, once erroneous behavior has been ob-
served. A ConfAid user reproduces the problem by exe-
cuting the application while ConfAid attaches to the ex-
ecuting application processes and monitors information
flow within them. For non-deterministic bugs, ConfAid
could potentially leverage one of several deterministic re-
play systems that can capture a buggy non-deterministic
execution and faithfully reproduce it for later analy-
sis [3, 18, 27, 36].
To use ConfAid, a user specifies: (1) which binaries
ConfAid should monitor, (2) the sources of configura-
tion data, and, as needed, (3) the erroneous external out-
put of the application. For simple applications, Conf-
Aid may monitor only a single process. For more com-
plicated applications, ConfAid dynamically attaches to
multiple specified processes and monitors inter-process
dependencies as described in Section 3.5. While Conf-
Aid could potentially monitor any process that receives
input via IPC or a network message from a process al-
ready monitored by ConfAid, we decided to only mon-
itor executables specified by the user in order to limit
the scope of analysis. Our prior experience with Auto-
Bash showed that many extraneous processes communi-
cate with processes being debugged via channels such
as files, pipes, and signals, yet these processes are not
needed to determine the root cause.
Similarly, we could potentially treat any source of in-
put to a program as a source of configuration data. How-
ever, such an approach would dramatically slow the anal-
ysis since most locations in the process address space
would come to depend on one or more inputs. In con-
trast, ConfAid only monitors input from designated con-
figuration sources. This makes ConfAid analysis more
tractable than generic taint tracking or program slic-
ing because the number of locations with dependencies
is small. Typically, the sources to monitor are self-
evident; e.g., httpd.conf is the configuration source
for Apache. Potentially, we could automate this process
by treating all inputs from specific locations (e.g., the
etc directory) or files with semantic keywords (such as
“k conf”) as configuration inputs.
Finally, a ConfAid user may designate specific error
conditions. ConfAid automatically treats assertion fail-
ures and exits with non-zero return codes as an erroneous
terminations. However, some misconfigurations lead not
to program termination, but instead to the process pro-
ducing erroneous output. We therefore allow the user to
specify a particular string expression as erroneous. Conf-
Aid monitors the system calls that write to network, ter-
minal, and other external output channels. When it finds
a matching output, it considers the output an error.
ConfAid outputs an ordered list of probable root
causes. Each entry in the list is a token from a config-
uration source; our results show that ConfAid typically
outputs the actual root cause as the first or second entry
in the list. This allows the ConfAid user to focus on one
or two specific configuration tokens when deciding how
to fix the problem. By finding the needle in the haystack,
ConfAid dramatically improves TTR.
3.2 Basic information flow analysis
In this section, we describe the basic information flow
analysis used by ConfAid. For simplicity of explana-
tion, we defer discussing optimizations and heuristics
until Sections 3.3 and 3.4. We also assume that ConfAid
is tracking only a single process; Section 3.5 describes
how we extend ConfAid analysis to multiple cooperat-
ing processes on one or more computers.
ConfAid dynamically monitors the information flow
from configuration sources through process memory and
registers to the point in the program execution when erro-
neous behavior is observed. It does so by using Pin [29]
to add custom logic, referred to as instrumentation, to the
process binary. As described below, ConfAid instrumen-
tation is executed before or after most x86 instructions
executed by a monitored application.
ConfAid uses taint tracking [34] to analyze informa-
tion flow. It inserts instrumentation into the binary that
monitors each system call such as read or pread that
could potentially read data from a configuration source.
If the source of the data returned by a system call was
specified as a configuration file, ConfAid annotates the
registers and memory addresses modified by the system
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) 239
240
if (c == 0) { /* c set to O in config file */
X = a; /* taken path */
} else {
y =b; /* alternate path */
}
z=d;
if (z) assert(); /* The erroneous behavior */
Figure 1: Example to illustrate causality tracking
call with a marker that indicates a dependency on a spe-
cific configuration token. Borrowing terminology from
the taint tracking literature, we refer to this marking as
the taint of the memory location. If an address or regis-
ter is tainted by a token, ConfAid believes that the value
at that location might be different if the value of the token
in the original configuration source were to change.
We use the notation, 7, to denote the taint set of vari-
able x. 7; is a set of configuration tokens; for instance, if
T, = { FOO, BAR }, ConfAid believes that the value of
variable x could change if the user were to modify either
the FOO or BAR tokens in the configuration file. In the ba-
sic information flow analysis, taints are binary (a location
is either tainted by a token or it is not); in Section 3.4, we
attach a weight to each taint.
Taint is propagated via data flow and control flow de-
pendencies. When a monitored process executes an in-
struction that modifies a memory address, register, or
CPU flag, the taint set of each modified location is set
to the union of the taint sets of the values read by the
instruction. For example, given the instruction x = y+ z
where the taint sets of y and z are T, and T, respectively,
the taint set of x, T,, becomes T, UT;. Intuitively, the
value of x might change if a configuration token were to
cause y or z to change prior to the execution of this in-
struction. For example, if T, = { FOO, BAR } and 7, =
{ FOO, BAZ },then7,={ FOO, BAR, BAZ }.
In traditional taint tracking for security purposes, con-
trol flow dependencies are often ignored to improve per-
formance because they are harder for an attacker to ex-
ploit. With ConfAid, however, we have found that track-
ing control flow dependencies is essential since they
propagate the majority of configuration-derived taint.
A naive approach to tracking control flow is to union
the taint set of a branch conditional with a running con-
trol flow dependency for the program. For example, on
executing the statement if (b), ConfAid could set the
control flow taint set, T.¢, to T.¢ UT. However, without
mechanisms to remove taint from T,¢, control flow taint
grows without limit. This causes too many false pos-
itives, i.e., ConfAid would identify most configuration
tokens as possible root causes.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
A more precise approach takes into account the ba-
sic block structure of a program. Consider the example
in Figure 1. Assume a, b, c, and d were read from a
configuration file and have taint sets T,, T,, T-, and Ty,
respectively (i.e., 7; is a set containing only configura-
tion token a). The value of c does not affect whether the
last two statements are executed, since they execute in all
possible paths (and therefore for all values of c). Thus,
T. should be removed from JT. before executing z = d.
When the program asserts, 7,.¢ should only include 7j in
the example, to correctly indicate that changing the value
of d might fix the problem.
ConfAid also tracks implicit control flow dependen-
cies. In Figure 1, the values of x and y depend on c when
the program asserts, since the occurrence of their assign-
ments to a and b depend on whether or not the branch is
taken. Note that y is still dependent on c even though the
else path is not taken by the execution since the value of
y might change if a configuration token is modified such
that the condition evaluates differently.
When the program executes a branch with a tainted
condition, ConfAid first determines the merge point (the
point where the branch paths converge) by consulting the
control flow graph. Prior to dynamic analysis, ConfAid
obtains the graph by using IDA Pro to statically analyze
the executable and any libraries it uses (e.g., Libc and
libssl).
For each tainted branch, ConfAid next explores each
alternate path that leads to the merge point. We define an
alternate path to be any path not taken by the actual pro-
gram execution that starts at a conditional branch instruc-
tion for which the branch condition is tainted by one or
more configuration values. ConfAid uses alternate path
exploration to learn which variables would have been as-
signed had the condition evaluated differently due to a
modified configuration value. The taint set of any vari-
able assigned on an alternate path is set to the union of
its previous taint set, the taint set of the conditional, and
the taint set of the variables read by the assigning instruc-
tion. In the example, 7, = 7, UT; U{T \T;}. In other
words, a configuration token affecting the previous value
of y could change, or c could change, causing the pre-
vious value of y to be overwritten. Finally, it might be
necessary for both c and b to change (as denoted by the
term {T. T;}) since c allows the alternate assignment,
and b may need to reflect a correct configuration value.
To evaluate an alternate path, ConfAid executes the
program by switching the condition outcome, similar
to the predicate switching approach used by Zhang et
al. [48] to explore implicit dependencies. ConfAid uses
copy-on-write logging to checkpoint and roll back ap-
plication state. When a memory address is first altered
along an alternate path, ConfAid saves the previous value
in an undo log. At the end of the execution, applica-
USENIX Association
USENIX Association
tion state is replaced with the previous values from the
log. ConfAid uses Pin mechanisms to checkpoint and
rollback the state of the processor, which includes the
registers and CPU flags. Since some alternate paths are
quite long, ConfAid uses a bounded horizon heuristic
described in Section 3.3.1 to limit the number of in-
structions it explores along each alternate path. Many
branches need not be explored since their conditions are
not tainted by any configuration token.
After exploring the alternate paths, ConfAid performs
a similar analysis for the path actually taken by the pro-
gram. This is the actual execution, so no undo log is
needed. In the example, analyzing the taken path would
derive T, = T, UT, U{T. A T;}.
ConfAid also uses alternate path exploration to learn
which paths avoid erroneous application behavior. Conf-
Aid considers an alternate path to avoid the erroneous
behavior if the path leads to a successful termination of
the program or if the merge point of the branch occurs
after the occurrence of the erroneous behavior in the pro-
gram (as determined by the static control flow graph).
ConfAid unions the taint sets of all conditions that led to
such alternate paths to derive its final result. This result is
the set of all configuration tokens which, if altered, could
cause the program to avoid the erroneous behavior.
Figure 2 shows four examples that illustrate how Conf-
Aid detects alternate paths that avoid the erroneous be-
havior. In case (a), the error occurs after the merge point
of the conditional branch. ConfAid determines that the
branch does not contribute to the error, because both
paths lead to the same erroneous behavior. In case (b),
the alternate path avoids the erroneous behavior because
the merge point occurs after the error, and the alternate
path itself does not exhibit any other error. In this case,
ConfAid considers tokens in the taint set of the branch
condition as possible root causes of the error, since if
the application had taken the alternate path, it could have
avoided the error. In case (c), the alternate path leads to
a different error (an assertion). Therefore, ConfAid does
not consider the taint of the branch as a possible root
cause because the alternate path would not lead to a suc-
cessful termination. In case (d), there are two alternate
paths, one of which leads to an assertion and one that
reaches the merge point. In this case, since there exists
an alternate path that avoids the erroneous behavior, con-
figuration tokens in the taint set of the branch condition
are possible root causes.
One limitation of evaluating an alternate path with
predicate switching is that switching a predicate out-
come, but not the underlying data values, may result in an
“unnatural” execution that leads to erroneous behaviors,
such as a crash due to a segmentation fault. In such cir-
cumstances, ConfAid aborts exploration of the alternate
path but conservatively retains the taint of the conditional
(a) (c)
alternate taken path
alternate taken path
path path error
assert
The alternate path leads to
another erroneous behavior
error
Error happens after
the merge point
(d)
alternate
(b) path taken path
taken path
alternate
path error sert
error
One of the alternate
paths avoids errors
Error happens in taken path
before the merge point
Figure 2: Examples illustrating ConfAid path analysis
branch in the possible root causes. This conservative be-
havior may lead to false positives if the alternate path
would in fact lead to a real error later in the execution.
The early abort of the alternate path may also lead to
false negatives due to unexplored variable assignments.
3.2.1 Abstracting library functions and system calls
There are three cases where ConfAid does not dynami-
cally analyze information flow. The first case is when the
application makes a system call. Since ConfAid does not
track taint inside the operating system, the information
flow analysis stops at the system call entry. The sec-
ond case is commonly executed standard library func-
tions such as malloc in libc and cryptographic func-
tions in 1ibss1l. ConfAid uses a primitive static analy-
sis for these functions to improve analysis speed while
still producing the identical effect on process taint values
that would have been produced by a fully-instrumented
execution. Since we abstract only functions in stan-
dard libraries, such taint abstractions are application-
independent. The final case is a small number of heavily
optimized libc functions for which IDA Pro does not
produce a complete static analysis.
To handle these cases, ConfAid uses taint abstraction
of the function (or system call). A taint abstraction spec-
ifies how taint is propagated from the inputs of the func-
tions to its outputs (e.g., return values and modified lo-
cation in the address space). When a process calls one
of these functions, ConfAid first executes the function
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) = 241
242
without any instrumentation and then uses the taint ab-
straction to modify the taints of the process memory and
registers.
3.3 Heuristics for performance
ConfAid uses two heuristics to simplify control flow
analysis. These heuristics eliminate exploration of some
alternate paths to concentrate on the paths that are most
likely to be useful in identifying the root cause. The
heuristics reduce analysis time but also introduce false
positives and negatives.
3.3.1 The bounded horizon heuristic
The first heuristic is the bounded horizon heuristic.
ConfAid only executes each alternate path for a fixed
number of instructions. By default, ConfAid uses a limit
of 80 instructions. All addresses and registers modified
within the limit are used to calculate information flow de-
pendencies after the merge point. Locations modified af-
ter the limit do not affect dependencies introduced at the
merge point. If an alternate path contains further tainted
conditional branches, ConfAid executes each path un-
til the limit is reached. For example, if the limit is 80
instructions and a tainted conditional branch occurs af-
ter executing 50 instructions, both paths from the new
branch are executed for an additional 30 instructions.
3.3.2 The single mistake heuristic
The second heuristic simplifies control flow analysis by
assuming that the configuration file contains only a lim-
ited number of erroneous tokens. By default, ConfAid
assumes that the configuration file contains a single error
— we refer to this as the single mistake heuristic.
To illustrate how this simplifies path exploration, con-
sider again the example in Figure 1. Recall that at the
time the assert statement is executed, 7. = JT, UT. U
{T. \T,}. The single mistake heuristic eliminates the
last term since that term requires the values of two to-
kens to change simultaneously. Similarly, ConfAid de-
rives T, = T, UT, during alternate path exploration. Note
that 7 no longer depends upon 7. This seems counter-
intuitive, but for the assignment y = b to occur in the
program, a token in 7, must change to cause the alternate
path to be taken. With the single mistake heuristic, a to-
ken in 7; but not in 7. cannot be the root cause, since one
token in 7 already must change.
More importantly, restricting the number of configu-
ration values that can change reduces the alternate paths
that are explored, as shown in Figure 3. The nested con-
dition, c2, can change only if a single configuration value
affects both cl and c2. If T; VT-2 = O, then the alternate
path of c2 need not be explored at all.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
if (cl == 0) { /* c1 set to O in config file */
} else {
if (c2 == 0) { /* c2 set to 0 also */
X= a;
} else {
y = b;
}
}
Figure 3: Example to illustrate alternate path pruning
To implement this heuristic, we introduce a new vari-
able, 7, that is the set of configuration options that, if
changed, would cause the execution of the program to
reach the current instruction. Initially, 7,;, is the set of
all configuration tokens. At each condition, c, Tz, does
not change along the taken path, but we set Ta, = Tay VT
along the alternate path. In Figure 3, Tay = To. VTe2 af-
ter the second condition. When 7,;; is 0, the alternate
path is explored no further. When a variable is assigned
along an alternate path, its taint value is set to the union
of its previous taint set and 7,;,. Thus, 7 = 7 U 7.1 and
Ty = Ty U (Ter NT2).
The single mistake heuristic may lead to false nega-
tives. In Figure 3, if cl and c2 are tainted by a disjoint
set of tokens, ConfAid will not explore the path on which
y is assigned to b, so it may miss the root cause if the
program later asserts based on the value of y. Potentially,
if ConfAid cannot find a root cause, we can relax the
single-mistake assumption by allowing ConfAid to as-
sume that two or more tokens are erroneous. In our ex-
periments to date, this heuristic has yet to trigger a false
negative.
3.4 Heuristics for reducing false positives
We originally designed ConfAid to use only the basic
taint tracking algorithm described in Section 3.2 with the
bounded horizon and single mistake heuristics. However,
our initial experiments with this design met with only
limited success. Typically, ConfAid would include the
root cause of a misconfiguration in its output set, yet the
cardinality of the output set would be very large. For
many bugs, ConfAid would return a significant fraction
of the tokens in the configuration file.
In analyzing our initial results, we realized that it was
insufficient to track information flow dependencies as bi-
nary values. In our design as described so far, two con-
figuration tokens are considered equal taint sources even
if one has a direct causal relationship to a location (e.g.,
the value in memory was read directly from the configu-
ration file) and another has a nebulous relationship (e.g.,
USENIX Association
USENIX Association
the taint was propagated along a long chain of condi-
tional assignments deep along alternate paths).
Another problem we noticed was that loops could
cause a location to become a global source and sink for
taint. For instance, Apache reads its configuration values
into a linked list structure, and then traverses the list in a
loop to find the value of a particular configuration token.
During the traversal, the program control flow picks up
taint from many configuration options, and these taints
are sometimes transferred to the configuration variable
that is the target of the search.
We realized that both of these problems were caused
by the implicit assumption in our design that all infor-
mation flow relationships should be treated equally. Es-
sentially, our design had no shades of gray: it either con-
sidered a location to be tainted by a token or it did not.
Based on this observation, we decided to modify our de-
sign to instead track taint as a floating-point weight rang-
ing in value between zero and one. For example, the taint
of x might be represented as { FOO:wfoo, BAR:Wpar }-
As before, this set indicates that modifying either token
FOO or BAR might change the value of x. However, if
Wfoo > Whar, FOO has a more direct relationship to x,
and hence is believed to be a better candidate for the root
cause of an error that depends on x.
We revised ConfAid to use heuristics to weight the
dependencies introduced by information flow differently,
with those relationships that are more likely to lead to the
root cause given a higher weight than those less likely to
lead to the root cause. We also modified ConfAid to or-
der the set of tokens on which an erroneous behavior de-
pends by their respective weights before outputting them.
Our weights are based on two heuristics. First, data
flow dependencies are assumed to be more likely to lead
to the root cause than control flow dependencies. Sec-
ond, control flow dependencies are assumed to be more
likely to lead to the root cause if they occur later in the
execution (i.e., closer to the erroneous behavior).
Specifically, we assign taints introduced by control
flow dependencies only half the weight of taints intro-
duced by data flow dependencies. Further, each nested
conditional branch reduces the weight of dependencies
introduced by prior branches in the nest by one half. We
chose a weight of 0.5 for speed: it can be implemented
efficiently with a vector bit shift.
For example, in Figure 4, the assignment x = a is a
data flow dependency, so T, = T, (any dependencies from
a are inherited at full weight). However, y inherits taint
from cl through a control flow dependency. Thus, 7, =
max(Ta, Ta), That is, we weight any taint from cl by
half, while taint inherited from a is given full weight.
We use a special max operator here rather than a simple
union operator, since the values are now floating point
rather than binary. Specifically, max(T;,T,) produces a
x =a;
if (cl == 0) { /* c1 set to O in config file */
y =a;
3 else {
Z=Dd;
}
if (c2 == 0) { /* c2 set to O in config file */
if (c3 == 0) { /* c3 also set to 0 */
w=a;
}
}
Figure 4: Example to illustrate the weighting heuristic
set that contains all tokens that occur in either 7, and T,.
If a token appears in only one of 7, or T,, its weight is
set to its weight in that set. If a token appears in both 7,
and T,, its weight is set to the maximum of its weight in
either set.
Similarly, T, = max(T,, iL) (recall that with binary
values, T, = T, UT; due to the single mistake heuris-
tic). When ConfAid explores an alternate path, it re-
places the intersection operator with a corresponding min
operator. Thus, in the prior example from Figure 3,
T= max(T,,min( 4, 42)),
Figure 4 also shows two nested conditions. In calculat-
ing the taint of w, condition c3 is considered more influ-
ential than condition c2 because it occurs later in the pro-
gram execution. Therefore T,, = max(Ty, 73 72), The
same weighting applies to alternate path execution; as-
signments on an alternate path starting at the c3 branch
are given twice the weight as those on an alternate path
starting at the c2 branch.
ConfAid also weights alternate paths that avoid the
erroneous behavior by their proximity to the point in
application execution where the behavior is exhibited.
Paths starting from the closest tainted conditional branch
that avoids the erroneous behavior are given full weight,
those from the next closest branch are given half weight,
and so on. Note that if a configuration token has a much
stronger weight on the condition of a distant branch than
any tokens for closer branches, ConfAid may still rank it
as the most likely root cause.
Of course, when programs do not behave as expected,
ConfAid’s heuristics may lead to incorrect results. For
example, an application could potentially execute a sub-
stantial amount of code between the point where the erro-
neous behavior occurs and the point where the program
outputs some value that exhibits the error (e.g., an error
message). If that code contains a condition tainted by
a configuration token other than the one that caused the
error and that condition changes the specific error mes-
sage that is generated, ConfAid might identify the wrong
token as the most likely root cause. While such a sce-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) = 243
244
nario is uncommon, we did observe a single Apache bug
(described further in the evaluation) in which ConfAid’s
heuristic failed in this manner.
3.5 Multi-process causality tracking
The most difficult configuration errors to troubleshoot
involve multiple interacting processes. Such processes
may be on a single computer, or they may reside on mul-
tiple computers connected by a network. To troubleshoot
such cases, ConfAid instruments multiple processes at
the same time and propagates taint information along
with the data sent when the processes communicate.
ConfAid supports processes that communicate using
sockets and files. The socket support includes Unix sock-
ets and pipes, as well as UDP and TCP sockets. Conf-
Aid instruments the system calls that create sockets and
pipes. It marks these objects as taint propagating chan-
nels if the destination is another instrumented process.
Then, ConfAid intercepts all sends and receives using
those channels. When data is sent, ConfAid appends a
header that indicates whether or not the data is tainted
and, when applicable, the exact taint of the data. Taint
information is propagated at per-byte granularity if the
taints of different bytes of the buffer are different. On
the receiving side, ConfAid extracts the header from the
received data and assigns the indicated taints to the re-
ceived data.
For files, ConfAid creates an auxiliary file with a spe-
cial “.confaid” extension when an instrumented process
writes tainted data to a file. The auxiliary file records
which bytes in the corresponding file are tainted and
the specific values of those taints. Like sockets, file
taint is recorded at granularities as small as one byte.
For instance, the file “foo.confaid” records the tainted
bytes in file “foo”. When an instrumented process reads
data from a file and a corresponding auxiliary file exists,
ConfAid sets the taints of bytes read from the file to the
values specified in the auxiliary file.
Since these operations are performed by PIN instru-
mentation immediately before and after system call exe-
cution, the taint propagation is hidden from the applica-
tion. No operating system modifications are needed.
3.6 Limitations and future work
Since configuration troubleshooting is complex, Conf-
Aid makes a number of assumptions to simplify its anal-
ysis. First, ConfAid only troubleshoots configuration
problems that lead to crashes, assertion failures, and in-
correct output; it does not yet help diagnose misconfig-
urations that cause poor performance. One approach to
tackling performance problems that we are investigating
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
is to first use statistical sampling to associate use of a bot-
tleneck resource such as disk or CPU with specific points
in the program execution. Then, ConfAid-style analysis
can determine which configuration tokens most directly
affect the frequency of execution of those points.
Second, like previous configuration troubleshooting
systems [38, 39], ConfAid currently assumes that the
configuration file contains only one erroneous token. If
fixing a particular error requires changing two tokens,
then ConfAid’s alternate path analysis may not identify
both tokens, as described in Section 3.3.2. However, if
a file contains two incorrect tokens that represent inde-
pendent mistakes, ConfAid can tackle the two errors se-
quentially by first identifying the token that leads to the
most immediate failure, and then identifying the other
token once the first error is corrected. The single mis-
take heuristic improves ConfAid’s performance by re-
ducing the set of possible taints tracked during dynamic
analysis. In the future, we plan to allow ConfAid to
track sets of two or more misconfigured tokens and mea-
sure the resulting performance overhead. Potentially, we
may use an expanding search technique in which Conf-
Aid initially performs an analysis assuming only a single
mistake, and then performs a lengthier analysis allowing
multiple mistakes if the first analysis does not yield sat-
isfactory results.
4 Evaluation
Our evaluation answers the following questions:
e How effective is ConfAid in identifying the root
cause of configuration problems?
e How long does ConfAid take to find the root cause?
4.1 Experimental setup
We evaluated ConfAid on three applications: the
OpenSSH server version 5.1, the Apache HTTP server
version 2.2.14, and the Postfix mail transfer agent version
2.7. All of our experiments were run on a Dell OptiPlex
980 desktop computer with an Intel Core i5 Dual Core
processor and 4GB of memory. The machine runs Linux
kernel version 2.6.21. For Apache, ConfAid instruments
a single process; for OpenSSH and Postfix, multiple pro-
cesses are instrumented.
To evaluate ConfAid, we manually injected errors into
correct configuration files. Then, we ran a test case that
caused the error we injected to be exhibited. We used
ConfAid to instrument the process (or processes) for that
application, and obtained the ordered list of root causes
found by ConfAid. We use two metrics to evaluate Conf-
Aid’s effectiveness: the ranking of the actual root cause,
USENIX Association
USENIX Association
i.e., the injected mistake, in the list returned by ConfAid
and the time to execute the instrumented application.
We used two different methods to generate configu-
ration errors. First, we injected 18 real-world configu-
ration errors that were reported in online forums, FAQ
pages, and application documentation. Second, we used
the ConfErr tool [25] to inject random errors into the con-
figuration files of the three applications.
4.2 Real-world misconfigurations
We searched forums, FAQ pages and configuration doc-
uments to find actual configuration problems that users
have experienced with our target applications. In total,
we chose 18 misconfigurations (5-7 for each application)
that were caused by errors in the configuration files. The
18 misconfigured values cover a range of data types, such
as binary options, enumerated types, numerical ranges,
and text entries such as server names. Table | lists the
configuration errors for each application, as well as the
ConfAid results.
In these experiments, ConfAid tracks dependencies
among multiple processes for all OpenSSH and Postfix
bugs. For OpenSSH, it instruments two processes that
communicate via Unix sockets. For Postfix, it instru-
ments between four and six processes that communicate
via Unix sockets and files; the number of instrumented
processes varies depending on how many processes are
started before a particular bug manifests. Multi-process
causality tracking is necessary to diagnose 4 out of 5
Postfix and 3 out of 7 OpenSSH bugs. For Apache, Conf-
Aid does not track dependencies across processes since
that application starts only a single process.
As shown in Table 1, ConfAid is highly effective in
pinpointing the root cause of misconfigurations. Conf-
Aid ranks the actual root cause first in 13 cases, and
second in the other 5. Sometimes, when the actual root
cause is ranked second, the token ranked first provides a
valuable clue to help debug the problem. For instance,
in Apache the actual error usually occurs nested inside a
section or directive command in the config file. For the
two Apache errors where the root cause is ranked second,
the top-ranked option is the section or directive contain-
ing the error.
The performance of ConfAid is reasonable. The time
to manifest the buggy behavior varies among applica-
tions. Postfix and OpenSSH take less than 2 minutes,
while Apache takes 2—3 minutes to complete. The av-
erage execution time of 1:32 minutes is much faster and
less frustrating than trying to fix such configuration er-
rors by looking at the logs, searching the Internet, and
asking colleagues for potential clues. For instance, the
6th Apache misconfiguration in Table | is taken from a
thread in linuxforums.org [28]. After trying to fix the
misconfiguration for quite a while, the user went to the
trouble of posting the question in the forum and waited
two days for an answer. In contrast, ConfAid identified
the root cause in less than 3 minutes.
4.3 Effect of the weighting heuristic
We next examine the effect of the weighting heuristic in-
troduced in Section 3.4. For each of the 18 real-world
misconfigurations, we disabled the heuristic and re-ran
ConfAid. With the heuristic disabled, ConfAid treats all
sources of information flow equally. Therefore, instead
of producing a ranked list of possible root causes, Conf-
Aid returns a single set of tokens, each of which is con-
sidered equally likely to be the root cause.
The last column of Table | shows the number of false
positives when the heuristic is disabled. In every case,
ConfAid identifies the correct root cause as one of the
returned tokens. However, the number of other tokens
returned varies substantially. Without the heuristic, there
were only two misconfigurations (the 6th OpenSSH bug
and the 5th Postfix bug) for which ConfAid produces no
false positives. For six other bugs, the number of false
positives is relatively low (less than 6). For the remain-
ing 10 bugs, ConfAid returns almost all options as pos-
sible root causes. Thus, without the weighting heuristic,
ConfAid is ineffective for 55% of the misconfigurations.
4.4 Effects of bounded horizon heuristic
We next investigated the effect of varying ConfAid’s
limit on the number of instructions executed along each
alternate path (discussed in Section 3.3.1) from the de-
fault value of 80 instructions. As Figure 5 shows, varying
the limit has substantially different effects on execution
time, depending on the application being instrumented.
For OpenSSH (bug #1), the execution time increases ap-
proximately linearly from 56 seconds with no alternate
path exploration to 2:29 minutes with a horizon of 1600
instructions. On the other hand, Postfix (bug #1), shows
an apparently exponential growth as the bound increases.
The execution time starts at 21 seconds with no alternate
path exploration and increases to 7:10 minutes for a hori-
zon of 800 instructions. With a horizon of 1600, ConfAid
analysis did not complete.
This difference in behavior derives from the nature of
the applications. We found that even with a limit of 80
instructions, more than 80% of the tainted conditional
branches in the OpenSSH bug reach their merge points
for all alternate paths. Increasing the horizon only affects
a small fraction of the branches since the rest are short
enough to finish within the limit. On the other hand, for
Postfix, less than 50% of the branches reach their merge
point within the limit of 80 instructions. As we raise the
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) 245
Application
OpenSSH
Server
Apache
HTTP Server
Postfix
246
Bug
Description of misconfiguration
The PermitRootLogin option is disabled. Therefore,
the user cannot ssh as root. The server keeps denying
permission although the password is entered correctly.
The server only has the PasswordAuthentication op-
tion enabled, while the user can only authenticate via
RSA keys.
The user does not have his public key in the directory
specified in the SSH server config file. Therefore, he
cannot authenticate.
The user is not in the AllowUsers list in the SSH con-
fig file. Therefore, he cannot connect to the server al-
though he enters the password correctly.
The MaxAuthTries option in SSH server config is set
too low. Therefore, the user is disconnected if she en-
ters her password incorrectly once.
The MaxStartups options is set to 1. Therefore, the
server refuses to start a new session, while another
unauthenticated session is still in progress.
The location of the server RSA key is not set correctly
in the config file. Therefore, the client fails to verify
the host key.
The path specified in the DocumentRoot option does
not have a corresponding <Directory> section. There-
fore, all accesses to this path are denied according to
the default policy.
The cgi-bin directory is ScriptAlised in the config file.
This prevents the DirectoryIndex from working as ex-
pected. Therefore, the user cannot access the index file
in the directory.
The cgi-bin directory is aliased in the config file. How-
ever, the corresponding Directory section does not pro-
vide sufficient permissions. Therefore, accesses to this
directory are denied.
A virtual host with the same interface coverage is set
for the HTTP server. This host points to a differ-
ent DocumentRoot which overwrites the default one.
Therefore, the user gets an index file with incorrect
content upon accessing the server DocumentRoot.
The cgi-bin directory is aliased and a CGI Handler is
activated in the config file. However, the correspond-
ing <Directory> section does not have the ExecCGI
option set. The user cannot access the executables in
this directory.
A specific directory in DocumentRoot is also aliased to
another directory outside DocumentRoot. Therefore,
accesses to files in the first directory are redirected to
the aliased directory, and the files are not found.
The mydestination option is not set correctly in the
Postfix config file. Therefore, Postfix cannot deliver
mail locally.
The myorigin option is set incorrectly in the Postfix
config file. Therefore, the next relay host bounces the
mail sent from the user’s machine to the Internet.
The relayhost option is set incorrectly. Therefore,
Postfix cannot forward the email sent from the user’s
machine to the Internet.
The type of alias_maps option is not supported in the
user’s machine. Therefore, Postfix fails to send any
mail locally or to the Internet.
The email address provided in luser-replay is not
reachable. Therefore, Postfix cannot redirect other
mail with wrong recipient to the luser-replay.
Total # of
options
47
47
48
49
47
47
47
88
89
89
93
89
89
27
27
29
29
29
ConfAid rank of
the root cause
2"4 (tied w/1)
1*(tied w/1)
gnd
gnd
1 (tied w/1)
2"4 (tied w/1)
2"4 (tied w/1)
1 st
1 st
1 (tied w/1)
1 st
Table 1: Results for 18 real-world configuration bugs
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
Execution
time
1m 16s
1m 10s
Sls
48s
1m 13s
9s
2m 46s
2m 45s
2m 45s
2m 59s
2m 46s
2m 47s
1m 10s
47s
1m 38s
# false positives
w/o weights
6
44
87
87
88
91
88
86
USENIX Association
USENIX Association
500 T T T T T T T T
OpenSSH Server —+—
= Postfix —*—
3 400 4
=
3
3
= 300 4
o
£
5 200 4
3
2
ij © -:100 4
0 1 1 1 1 1 1 1 1
0 200 400 600 800 1000 1200 1400 1600 1800
Maximum # of explored instructions
Figure 5: The effect of varying the horizon
limit, the percentage of the completed branches increases
slowly to 60%.
To summarize, we found that there is no single limit
that works best for all applications. Consequently, we
envision that we could augment ConfAid to use an iter-
ative search process in which it would start with a small
horizon to generate results quickly, then continue to exe-
cute with larger horizons to refine the results.
4.5 Random fault injection
We next used ConfErr [25] to randomly generate config-
uration errors. ConfErr uses human error models rooted
in psychology and linguistics to generate realistic config-
uration mistakes. We used ConfErr to produce 20 errors
for each application. We then injected the errors one by
one and measured the effectiveness and performance of
ConfAid.
As shown in Table 2, ConfAid performs very well on
these errors. The average time to execute all three appli-
cations is lower than the average execution time for the
real-world errors used in the previous section. The main
reason for this difference is that the real-world errors are
often more complex than the randomly-generated ones.
Therefore, it takes more time for the application to man-
ifest the buggy behavior for real-world errors.
For the randomly generated errors, ConfAid instru-
ments up to two processes for OpenSSH and up to six
processes for Postfix. However, many faults are exhib-
ited before these applications start additional processes;
in such cases, ConfAid only instruments one process.
For OpenSSH, ConfAid successfully pinpointed the
root cause (where we define success as listing the actual
root cause as one of the top two options) for 95% of the
bugs. For the last bug, ConfAid could not run to com-
pletion due to unsupported system calls used in the code
path. We could remedy this by abstracting more calls.
ConfAid also successfully diagnoses 95% of the
Apache errors. For the remaining error, ConfAid ranks
the root cause 9th. The configuration error is that the
DirectoryIndex file for the main document root is listed
incorrectly in the Apache configuration file. The Directo-
ryIndex file is the file that Apache serves if that directory
is accessed without mentioning a specific file. For in-
stance, accessing http://server.com/images/ will
return the Directory Index file listed for the images direc-
tory. However, the Indexes option is also activated for
the document root directory. This option allows Apache
to send the list of the files in the directory if no specific
file in that directory is requested. The combination of
these two options causes Apache to serve the list of files
in the main document directory instead of the index file.
ConfAid determines that the content sent to the user is
dependent on the Indexes and related options first and
the DirectoryIndex option next. Thus, the root cause gets
ranked lower in the list. This ordering is a direct result
of the heuristic discussed in Section 3.4 that considers
branches closer to the erroneous behavior to be more
likely to lead to the root cause than those farther away.
For Postfix, ConfAid diagnoses 85% of the errors ef-
fectively. The remaining 3 errors are due to missing
configuration options. Currently, ConfAid only consid-
ers all tokens present in the configuration file as possi-
ble sources of the root cause. If a default value can be
overridden by a token not actually in the file, then Conf-
Aid will not detect the missing token as a possible root
cause. Based on these results, we plan to extend our al-
ternate path analysis to look for tokens that could be read
from the config file along branches that are not actually
executed. We can taint variables modified along those
branches with a value that is dependent upon the branch
conditions that led to that path.
Overall, ConfAid successfully diagnosed 55 out of 60
random errors by ranking the actual root cause first or
second. Out of the remaining 5 errors, we believe that 4
(the OpenSSH server error and the three Postfix errors)
can be diagnosed with further improvements to the Conf-
Aid implementation. The remaining error (the Apache
error) is a direct result of our weighting heuristic and
seems hard for ConfAid to diagnose correctly.
5 Related work
Several prior research efforts have applied different tech-
niques to the problem of configuration troubleshooting.
Unlike ConfAid, most prior systems have taken a black
box approach that uses only state external to the applica-
tion being debugged to infer the problem.
PeerPressure [38] and its predecessor, Strider [39], use
statistical methods to compare configuration state in the
Windows registry on different machines. When a value
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) = 247
248
Application | root causes | root causes ranked root causes root causes ranked | root causes ranked | Avg. time
ranked first first with one tie ranked second | second with one tie | worse than second to run
OpensSH_| 17 85%) | 16%) io [| 0 | —*16%)
75% | __16% [0 | 16% 16%)
sm [| 0 | 200% [0 315%)
Table 2: Random fault injection results
on a machine exhibiting erroneous behavior differs from
the value usually chosen by other machines, PeerPres-
sure flags the value as a potential error. This approach
works well as long as the majority configuration is ap-
propriate for the target machine; however, PeerPressure
and Strider cannot separate custom configuration vari-
ables from erroneous ones since they do not observe how
applications actually use those values. In contrast, Conf-
Aid can differentiate these cases by observing how the
values are used inside the application binary.
Chronus [40] also compares multiple configuration
states, Instead of comparing states across computers, it
uses virtual machine checkpoint and rollback to “time
travel” through states on the same machine, looking for
the instance in which the program behavior on a particu-
lar test case switched from correct to incorrect.
Other projects monitor state external to applications
as they run. Cohen et al. [15] use statistical techniques
to help troubleshoot performance issues by correlating
those issues with low-level performance statistics for the
CPU, disk, and other system components. AutoBash [37]
traces causality inside the OS by monitoring system call
execution, but treats execution inside each process as a
black box. AutoBash can suggest that a particular con-
figuration file may be erroneous, but it cannot identify
the specific value within the file that is at fault.
Our previous work on misconfiguration diagnosis [4]
uses the application’s system call trace to extract the files
and processes on which the application causally depends.
It then generates a signature based on those dependencies
to represent the misconfiguration and search for the sig-
nature in a database of known bugs. Clarify [21] uses
similar execution signatures to improve error reporting.
It uses program features such as function call counts, call
sites, and stack dumps to generate the signatures. The
improved error reporting, although helpful, does not di-
agnose the root cause.
In contrast to all these projects, ConfAid takes a white
box approach to configuration troubleshooting by moni-
toring causality within the program binary as it executes.
Thus, ConfAid can observe the actual dependencies as
they are introduced rather than inferring them through
statistical and other methods.
Two recent systems apply white box analysis to a re-
lated problem: helping developers replicate a problem
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
experienced in the field. SherLog [43] and ESD [44]
both use static analysis and symbolic execution to infer
the execution path of the application. SherLog uses log
messages and ESD leverages the bug report generated by
the application to constrain the execution path. Both of
these systems can replicate an execution path that derives
from a misconfiguration. However, they make different
design decisions than ConfAid, driven by their differ-
ent use case. They both require application source code,
and SherLog also may require guidance from developers
about which functions should be symbolically executed.
This is appropriate for a tool used by software experts,
but less so for one like ConfAid that is targeted at ad-
ministrators and users. More generally, symbolic execu-
tion systems have been applied to model checking file
systems and other complex software systems [9, 41].
A number of systems trace causality external to pro-
cesses to debug configuration and performance issues in
distributed systems. For example, the work of Aguilera
et al. [2] and Magpie [5, 6] trace RPCs and other net-
work communication to debug performance problems.
Causeway [11] allows applications to inject metadata
that follows causal paths for distributed applications.
Pinpoint [13] traces middleware and communications be-
tween components in a distributed system and statis-
tically correlates traces with success and failure data.
Follow-on work to Pinpoint [12] adds the abstraction of
causal paths that link black-box components. ConfAid
and these systems share the common idea of propagat-
ing causal information among distributed components;
however, ConfAid also propagates causal information
within processes, which allows it to precisely determine
the causal relationships between inputs and outputs.
More generally, many systems reason about causal in-
teractions in the operating system and in distributed sys-
tems. For example, taint tracking [34] monitors data flow
dependencies to determine when input data is used in
an insecure manner. ConfAid uses the same approach
for data flow analysis, but applies it to a different do-
main. Dytan [14] proposes a generic dynamic taint anal-
ysis framework to ease the implementation of various
taint-based techniques. ConfAid enhances the basic dy-
namic taint analysis with essential heuristics and ap-
plies it to configuration troubleshooting problem. Red-
Flag [17] uses data flow analysis to reduce the leaks of
USENIX Association
USENIX Association
sensitive information by personal machines. Resin [42]
uses application-level data flow assertions to improve
the security of applications. Decentralized informa-
tion flow [32, 45] monitors both control flow and data
flow dependencies to determine if a code component
leaks information that it is not authorized to divulge.
BackTracker [26] traces causal interactions to determine
what state has been changed during an intrusion. As-
bestos [19] and HiStar [46] monitor causality in the OS
to prevent inadvertent disclosure of private data.
Program slicing [1, 48, 47], intended to aid in debug-
ging, is a more general approach that determines which
statements could affect the value of a variable using a
backward or forward computations. ConfAid applies
similar data and control flow analysis techniques to a new
problem, namely determining the root causes of miscon-
figurations.
6 Conclusion
Configuration errors are costly, time-consuming, and
frustrating to troubleshoot. | ConfAid makes trou-
bleshooting easier by pinpointing the specific token in a
configuration file that led to an erroneous behavior. Com-
pared to prior approaches, ConfAid distinguishes itself
by analyzing causality within processes as they execute
without the need for application source code. It propa-
gates causal dependencies among multiple processes and
outputs a ranked list of probable root causes. Our results
show that ConfAid usually lists the actual root cause as
the first or second entry in this list. Thus, ConfAid can
substantially reduce total time to recovery and perhaps
make configuration problems a little less frustrating.
Acknowledgments
We thank the anonymous reviewers and our shepherd, Shan Lu, for
comments that improved this paper. We also thank the ConfErr team for
helping us use their tool. This research was supported by NSF award
CNS-1017148. The views and conclusions contained in this document
are those of the authors and should not be interpreted as representing
the official policies, either expressed or implied, of NSF, the University
of Michigan, or the U.S. government.
References
[1] H. Agrawal and J. R. Horgan. Dynamic program slicing. In
ACM SIGPLAN Conference on Programming Language Design
and Implementation, pages 246-256, 1990.
[2] M. K. Aguilera, J. C. Mogul, J. L. wiener, P. Reynolds, and
A. Muthitacharoen. Performance debugging for distributed sys-
tems of black boxes. In Proceedings of the 19th ACM Symposium
on Operating Systems Principles, pages 74-89, Bolton Landing,
NY, October 2003.
[3] G. Altekar and I. Stoica. ODR: Output-deterministic replay for
multicore debugging. In Proceedings of the 22nd ACM Sympo-
sium on Operating Systems Principles, pages 193-206, October
2009.
[4]
[5]
[6
=i
[7]
[8]
[9]
[10]
[11]
[12]
[13]
[14]
[15]
[16]
[17]
[18]
[19]
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
M. Attariyan and J. Flinn. Using causality to diagnose config-
uration bugs. In Proceedings of the USENIX Annual Technical
Conference, pages 171-177, Boston, MA, June 2008.
P. Barham, A. Donnelly, R. Isaacs, and R. Mortier. Using Magpie
for request extraction and workload modelling. In Proceedings of
the 6th Symposium on Operating Systems Design and Implemen-
tation, pages 259-272, San Francisco, CA, December 2004.
P. Barham, R. Isaacs, R. Mortier, and D. Narayanan. Magpie: On-
line modelling and performance-aware systems. In Proceedings
of the 9th Workshop on Hot Topics in Operating Systems, Lihue,
HI, May 2003.
A. B. Brown and D. A. Patterson. To err is human. In DSN
Workshop on Evaluating and Architecting System Dependability,
Goteborg, Sweden, July 2001.
A. B. Brown and D. A. Patterson. Undo for operators: Building
an undoable e-mail store. In Proceedings of the 2003 USENIX
Technical Conference, San Antonio, TX, June 2003.
C. Cadar, D. Dunbar, and D. Engler. KLEE: Unassisted and au-
tomatic generation of high-coverage tests for complex systems
programs. In Usenix Symposium on Operating System Design
and Implementation (OSDI), pages 209-224, December 2008.
M. Castro and B. Liskov. Proactive recovery in a Byzantine-fault-
tolerant system. In Proceedings of the 4th Symposium on Operat-
ing Systems Design and Implementation, San Diego, CA, October
2000.
A. Chanda, K. Elmeleegy, A. L. Cox, and W. Zwaenepoel. Cause-
way: Operating system support for controlling and analyzing the
execution of distributed programs. In Proceedings of the 10th
Workshop on Hot Topics in Operating Systems (HotOS-X), Santa
Fe, NM, June 2005.
M. Y. Chen, A. Accardi, E. Kiciman, J. Lloyd, D. Patterson,
A. Fox, and E. Brewer. Path-based failure and evolution man-
agement. In Proceedings of the Ist Symposium on Networked
Systems Design and Implementation (NSDI), San Francisco, CA,
March 2004.
M. Y. Chen, E. Kiciman, E. Fratkin, A. Fox, and E. Brewer.
Pinpoint: Problem, determination in large, dynamic Inter-
net services. In Proceedings of the International Conference
on Dependable Systems and Networks (DSN), pages 595-604,
Bethesda, MD, June 2002.
J. Clause, W. Li, and A. Orso. Dytan: A generic dynamic taint
analysis framework. In Jn Proceedings of the International Sym-
posium on Software Testing and Analysis, pages 196-206, July
2007.
I. Cohen, M. Goldszmidt, T. Kelly, J. Symons, and J. Chase. Cor-
relating instrumentation data to system states: A building block
for automated diagnosis and control. In Proceedings of the 6th
Symposium on Operating Systems Design and Implementation,
pages 231-244, San Francisco, CA, December 2004.
Computing Research Association. Final report of the CRA con-
ference on grand research challenges in information systems.
Technical report, September 2003.
L. P. Cox and P. Gilbert. RedFlag: Reducing inadvertant leaks by
personal machines. Technical Report MSR-TR-2009-02, Duke
University, 2009.
G. W. Dunlap, S. T. King, S. Cinar, M. A. Basrai, and P. M. Chen.
ReVirt: Enabling intrusion analysis through virtual-machine log-
ging and replay. In Proceedings of the 5th Symposium on Operat-
ing Systems Design and Implementation, pages 211-224, Boston,
MA, December 2002.
P. Efstathopoulos, M. Krohn, S. VanDeBogart, C. Frey,
D. Ziegler, E. Kohler, D. Mazieres, F. Kaashoek, and R. Morris.
249
250
[20]
[21]
[22]
[23]
[24]
[25]
[26]
[27]
[28]
[29]
[30]
[31]
[33]
[34]
[35]
Labels and event processes in the Asbestos operating system. In
Proceedings of the 20th ACM Symposium on Operating Systems
Principles, Brighton, United Kingdom, October 2005.
J. Gray. Why do computer stop and what can be done about it?
Technical Report 85.7, Tandem Corp., June 1985.
J. Ha, C. J. Rossbach, J. V. Davis, I. Roy, H. E. Ramadan, D. E.
Porter, D. L. Chen, and E. Witchel. Improved error reporting for
software that uses black-box components. In Proceedings of the
Conference on Programming Language Design and Implementa-
tion 2007, pages 101-111, San Diego, CA, 2007.
IDA Pro disassembler. http://www.hex-rays.com/idapro.
F. Junqueira, Y. J. Song, and B. Reed. BFT for the skeptics.
In ACM Symposium on Operating Systems Principles: Work in
Progress Session, October 2009.
A. Kapoor. Web-to-host: Reducing total cost of ownership. Tech-
nical Report 200503, The Tolly Group, May 2000.
L. Keller, P. Upadhyaya, and G. Candea. ConfErr: A tool for
assessing resilience to human configuration errors. In Proceed-
ings of the International Conference on Dependable Systems and
Networks (DSN), pages 157-166, Anchorage, AK, June 2008.
S. T. King and P. M. Chen. Backtracking intrusions. In Proceed-
ings of the 19th ACM Symposium on Operating Systems Princi-
ples, pages 223-236, Bolton Landing, NY, October 2003.
D. Lee, B. Wester, K. Veeraraghavan, P. M. Chen, J. Flinn, and
S. Narayanasamy. Respec: Efficient online multiprocessor re-
play via speculation and external determinism. In Proceedings of
the 15th International Conference on Architectural Support for
Programming Languages and Operating Systems, pages 77-89,
Pittsburgh, PA, March 2010.
http://www. linuxforums.org/forum/servers/125833-solved-
apache-wont-follow-symlinks.html.
C.-K. Luk, R. Cohn, R. Muth, H. Patil, A. Klauser, G. Lowney,
S. Wallace, V. J. Reddi, and K. Hazelwood. Pin: Building cus-
tomized program analysis tools with dynamic instrumentation. In
Programming Language Design and Implementation, pages 190-
200, Chicago, IL, June 2005.
R. E. Lyons and W. Vanderkulk. The use of triple-modular redun-
dancy to improve computer reliability. IBM Journal of Research
and Development, 6(2):200-209, 1962.
B. Murphy and T. Gent. Measuring system and software relia-
bility using an automated data collection process. Quality and
Reliability Engineering International, 11(5), 1995.
A. C. Myers. JFlow: Practical mostly-static information flow
control. In Proceedings of the Annual Symposium on Principles
of Programming Languages, pages 228-241, San Antonio, TX,
January 1999.
K. Nagaraja, F. Oliveria, R. Bianchini, R. P. Martin, and
T. Nguyen. Understanding and dealing with operator mistakes
in Internet services. In Proceedings of the 6th Symposium on Op-
erating Systems Design and Implementation, pages 61-76, San
Francisco, CA, December 2004.
J. Newsome and D. Song. Dynamic taint analysis: Automatic
detection, analysis, and signature generation of exploit attacks on
commodity software. In In Proceedings of the 12th Network and
Distributed Systems Security Symposium, February 2005.
D. Oppenheimer, A. Ganapathi, and D. A. Patterson. Why do
Internet services fail, and what can be done about it? In Proceed-
ings of the 4th USENIX Symposium on Internet Technologies and
Systems (USITS), March 2003.
S. Park, Y. Zhou, W. Xiong, Z. Yin, R. Kaushik, K. H. Lee, and
S. Lu. PRES: Probabilistic replay with execution sketching on
multiprocessors. In Proceedings of the 22nd ACM Symposium on
Operating Systems Principles, pages 177-191, October 2009.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
[37]
[39]
[40]
[41]
[42]
[43]
[44]
[45]
[46]
[47]
[48]
Y.-Y. Su, M. Attariyan, and J. Flinn. AutoBash: Improving con-
figuration management with operating system causality analysis.
In Proceedings of the 21st ACM Symposium on Operating Sys-
tems Principles, pages 237-250, Stevenson, WA, October 2007.
H. J. Wang, J. C. Platt, Y. Chen, R. Zhang, and Y.-M. Wang. Au-
tomatic misconfiguration troubleshooting with PeerPressure. In
Proceedings of the 6th Symposium on Operating Systems Design
and Implementation, pages 245-257, San Francisco, CA, Decem-
ber 2004.
Y.-M. Wang, C. Verbowski, J. Dunagan, Y. Chen, H. J. Wang,
C. Yuan, and Z. Zhang. STRIDER: A black-box, state-based ap-
proach to change and configuration management and support. In
Proceedings of the USENIX Large Installation Systems Adminis-
tration Conference, pages 159-172, October 2003.
A. Whitaker, R. S. Cox, and S. D. Gribble. Configuration debug-
ging as search: Finding the needle in the haystack. In Proceed-
ings of the 6th Symposium on Operating Systems Design and Im-
plementation, pages 77-90, San Francisco, CA, December 2004.
J. Yang, C. Sar, and D. Engler. eXplode: a lightweight, general
system for finding serious storage system errors. In Proceedings
of the 7th Symposium on Operating Systems Design and Imple-
mentation, pages 131-146, Seattle, WA, November 2006.
A. Yip, X. Wang, N. Zeldovich, and M. F. Kaashoek. Improving
application security with data flow assertions. In Proceedings
of the 22nd ACM Symposium on Operating Systems Principles,
pages 291-304, October 2009.
D. Yuan, H. Mai, W. Xiong, L. Tan, Y. Zhou, and S. Pasupa-
thy. SherLog: Error diagnosis by connecting clues from run-time
logs. In Proceedings of the Fifteenth International Conference on
Architectural Support for Programming Languages and Operat-
ing Systems, pages 143-154, Pittsburgh, PA, March 2010.
C. Zamfir and G. Candea. Execution Synthesis: A Technique for
Automated Software Debugging. In Proceedings of the 2010 Eu-
ropean Conference on Computer Systems (EuroSys), pages 321-
334, April 2010.
S. Zdancewic, L. Zheng, N. Nystrom, and A. C. Myers. Un-
trusted hosts and confidentiality: Secure program partitioning. In
Proceedings of the 18th ACM Symposium on Operating Systems
Principles, pages 1-14, Banff, Canada, October 2001.
N. Zeldovich, S. Boyd-Wickizer, E. Kohler, and D. Mazieres.
Making information flow explicit in HiStar. In Proceedings of
the 7th Symposium on Operating Systems Design and Implemen-
tation, pages 263-278, Seattle, WA, November 2006.
X. Zhang, N. Gupta, and R. Gupta. Pruning dynamic slices with
confidence. In Proceedings of the ACM SIGPLAN 2006 Con-
ference on Programming Language Design and Implementation,
pages 169-180, June 2006.
X. Zhang, S. Tallam, N. Gupta, and R. Gupta. Towards locating
execution omission errors. In Proceedings of the ACM SIGPLAN
2007 Conference on Programming Language Design and Imple-
mentation, pages 415-424, June 2007.
USENIX Association
USENIX Association
Large-scale Incremental Processing
Using Distributed Transactions and Notifications
Daniel Peng and Frank Dabek
dpeng@google.com,
fdabek@google.com
Google, Inc.
Abstract
Updating an index of the web as documents are
crawled requires continuously transforming a large
repository of existing documents as new documents ar-
rive. This task is one example of a class of data pro-
cessing tasks that transform a large repository of data
via small, independent mutations. These tasks lie in a
gap between the capabilities of existing infrastructure.
Databases do not meet the storage or throughput require-
ments of these tasks: Google’s indexing system stores
tens of petabytes of data and processes billions of up-
dates per day on thousands of machines. MapReduce and
other batch-processing systems cannot process small up-
dates individually as they rely on creating large batches
for efficiency.
We have built Percolator, a system for incrementally
processing updates to a large data set, and deployed it
to create the Google web search index. By replacing a
batch-based indexing system with an indexing system
based on incremental processing using Percolator, we
process the same number of documents per day, while
reducing the average age of documents in Google search
results by 50%.
1. Introduction
Consider the task of building an index of the web that
can be used to answer search queries. The indexing sys-
tem starts by crawling every page on the web and pro-
cessing them while maintaining a set of invariants on the
index. For example, if the same content is crawled un-
der multiple URLs, only the URL with the highest Page-
Rank [28] appears in the index. Each link is also inverted
so that the anchor text from each outgoing link is at-
tached to the page the link points to. Link inversion must
work across duplicates: links to a duplicate of a page
should be forwarded to the highest PageRank duplicate
if necessary.
This is a bulk-processing task that can be expressed
as a series of MapReduce [13] operations: one for clus-
tering duplicates, one for link inversion, etc. It’s easy to
maintain invariants since MapReduce limits the paral-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
lelism of the computation; all documents finish one pro-
cessing step before starting the next. For example, when
the indexing system is writing inverted links to the cur-
rent highest-PageRank URL, we need not worry about
its PageRank concurrently changing; a previous MapRe-
duce step has already determined its PageRank.
Now, consider how to update that index after recrawl-
ing some small portion of the web. It’s not sufficient to
run the MapReduces over just the new pages since, for
example, there are links between the new pages and the
rest of the web. The MapReduces must be run again over
the entire repository, that is, over both the new pages
and the old pages. Given enough computing resources,
MapReduce’s scalability makes this approach feasible,
and, in fact, Google’s web search index was produced
in this way prior to the work described here. However,
reprocessing the entire web discards the work done in
earlier runs and makes latency proportional to the size of
the repository, rather than the size of an update.
The indexing system could store the repository in a
DBMS and update individual documents while using
transactions to maintain invariants. However, existing
DBMSs can’t handle the sheer volume of data: Google’s
indexing system stores tens of petabytes across thou-
sands of machines [30]. Distributed storage systems like
Bigtable [9] can scale to the size of our repository but
don’t provide tools to help programmers maintain data
invariants in the face of concurrent updates.
An ideal data processing system for the task of main-
taining the web search index would be optimized for in-
cremental processing; that is, it would allow us to main-
tain a very large repository of documents and update it
efficiently as each new document was crawled. Given
that the system will be processing many small updates
concurrently, an ideal system would also provide mech-
anisms for maintaining invariants despite concurrent up-
dates and for keeping track of which updates have been
processed.
The remainder of this paper describes a particular in-
cremental processing system: Percolator. Percolator pro-
vides the user with random access to a multi-PB reposi-
tory. Random access allows us to process documents in-
251
252
QO
>
=
5
x
a
oO
2
°
Application
Percolator Library
Bigtable Tabletserver
Figure 1: Percolator and its dependencies
dividually, avoiding the global scans of the repository
that MapReduce requires. To achieve high throughput,
many threads on many machines need to transform the
repository concurrently, so Percolator provides ACID-
compliant transactions to make it easier for programmers
to reason about the state of the repository; we currently
implement snapshot isolation semantics [5].
In addition to reasoning about concurrency, program-
mers of an incremental system need to keep track of the
state of the incremental computation. To assist them in
this task, Percolator provides observers: pieces of code
that are invoked by the system whenever a user-specified
column changes. Percolator applications are structured
as a series of observers; each observer completes a task
and creates more work for “downstream” observers by
writing to the table. An external process triggers the first
observer in the chain by writing initial data into the table.
Percolator was built specifically for incremental pro-
cessing and is not intended to supplant existing solutions
for most data processing tasks. Computations where the
result can’t be broken down into small updates (sorting
a file, for example) are better handled by MapReduce.
Also, the computation should have strong consistency
requirements; otherwise, Bigtable is sufficient. Finally,
the computation should be very large in some dimen-
sion (total data size, CPU required for transformation,
etc.); smaller computations not suited to MapReduce or
Bigtable can be handled by traditional DBMSs.
Within Google, the primary application of Percola-
tor is preparing web pages for inclusion in the live web
search index. By converting the indexing system to an
incremental system, we are able to process individual
documents as they are crawled. This reduced the aver-
age document processing latency by a factor of 100, and
the average age of a document appearing in a search re-
sult dropped by nearly 50 percent (the age of a search re-
sult includes delays other than indexing such as the time
between a document being changed and being crawled).
The system has also been used to render pages into
images; Percolator tracks the relationship between web
pages and the resources they depend on, so pages can be
reprocessed when any depended-upon resources change.
2 Design
Percolator provides two main abstractions for per-
forming incremental processing at large scale: ACID
transactions over a random-access repository and ob-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
servers, a way to organize an incremental computation.
A Percolator system consists of three binaries that run
on every machine in the cluster: a Percolator worker, a
Bigtable [9] tablet server, and a GFS [20] chunkserver.
All observers are linked into the Percolator worker,
which scans the Bigtable for changed columns (“noti-
fications”) and invokes the corresponding observers as
a function call in the worker process. The observers
perform transactions by sending read/write RPCs to
Bigtable tablet servers, which in turn send read/write
RPCs to GFS chunkservers. The system also depends
on two small services: the timestamp oracle and the
lightweight lock service. The timestamp oracle pro-
vides strictly increasing timestamps: a property required
for correct operation of the snapshot isolation protocol.
Workers use the lightweight lock service to make the
search for dirty notifications more efficient.
From the programmer’s perspective, a Percolator
repository consists of a small number of tables. Each
table is a collection of “cells” indexed by row and col-
umn. Each cell contains a value: an uninterpreted array of
bytes. (Internally, to support snapshot isolation, we rep-
resent each cell as a series of values indexed by times-
tamp.)
The design of Percolator was influenced by the re-
quirement to run at massive scales and the lack of a
requirement for extremely low latency. Relaxed latency
requirements let us take, for example, a lazy approach
to cleaning up locks left behind by transactions running
on failed machines. This lazy, simple-to-implement ap-
proach potentially delays transaction commit by tens of
seconds. This delay would not be acceptable in a DBMS
running OLTP tasks, but it is tolerable in an incremental
processing system building an index of the web. Percola-
tor has no central location for transaction management;
in particular, it lacks a global deadlock detector. This in-
creases the latency of conflicting transactions but allows
the system to scale to thousands of machines.
2.1 Bigtable overview
Percolator is built on top of the Bigtable distributed
storage system. Bigtable presents a multi-dimensional
sorted map to users: keys are (row, column, times-
tamp) tuples. Bigtable provides lookup and update oper-
ations on each row, and Bigtable row transactions enable
atomic read-modify-write operations on individual rows.
Bigtable handles petabytes of data and runs reliably on
large numbers of (unreliable) machines.
A running Bigtable consists of a collection of tablet
servers, each of which is responsible for serving several
tablets (contiguous regions of the key space). A master
coordinates the operation of tablet servers by, for exam-
ple, directing them to load or unload tablets. A tablet is
stored as a collection of read-only files in the Google
USENIX Association
USENIX Association
SSTable format. SSTables are stored in GFS; Bigtable
relies on GFS to preserve data in the event of disk loss.
Bigtable allows users to control the performance charac-
teristics of the table by grouping a set of columns into
a locality group. The columns in each locality group are
stored in their own set of SSTables, which makes scan-
ning them less expensive since the data in other columns
need not be scanned.
The decision to build on Bigtable defined the over-
all shape of Percolator. Percolator maintains the gist of
Bigtable’s interface: data is organized into Bigtable rows
and columns, with Percolator metadata stored along-
side in special columns (see Figure 5). Percolator’s
API closely resembles Bigtable’s API: the Percolator li-
brary largely consists of Bigtable operations wrapped in
Percolator-specific computation. The challenge, then, in
implementing Percolator is providing the features that
Bigtable does not: multirow transactions and the ob-
server framework.
2.2 Transactions
Percolator provides cross-row, cross-table transac-
tions with ACID snapshot-isolation semantics. Percola-
tor users write their transaction code in an imperative
language (currently C++) and mix calls to the Percola-
tor API with their code. Figure 2 shows a simplified ver-
sion of clustering documents by a hash of their contents.
In this example, if Commit() returns false, the transac-
tion has conflicted (in this case, because two URLs with
the same content hash were processed simultaneously)
and should be retried after a backoff. Calls to Get(Q) and
Commit() are blocking; parallelism is achieved by run-
ning many transactions simultaneously in a thread pool.
While it is possible to incrementally process data with-
out the benefit of strong transactions, transactions make
it more tractable for the user to reason about the state of
the system and to avoid the introduction of errors into
a long-lived repository. For example, in a transactional
web-indexing system the programmer can make assump-
tions like: the hash of the contents of a document is al-
ways consistent with the table that indexes duplicates.
Without transactions, an ill-timed crash could result in a
permanent error: an entry in the document table that cor-
responds to no URL in the duplicates table. Transactions
also make it easy to build index tables that are always
up to date and consistent. Note that both of these exam-
ples require transactions that span rows, rather than the
single-row transactions that Bigtable already provides.
Percolator stores multiple versions of each data item
using Bigtable’s timestamp dimension. Multiple versions
are required to provide snapshot isolation [5], which
presents each transaction with the appearance of reading
from a stable snapshot at some timestamp. Writes appear
in a different, later, timestamp. Snapshot isolation pro-
bool UpdateDocument(Document doc) {
Transaction t(&cluster);
t.Set(doc.url(), "contents", "document", doc.contents());
int hash = Hash(doc.contents());
// dups table maps hash — canonical URL
string canonical;
if (!t-Get(hash, "canonical-url", "dups", &canonical)) {
// No canonical yet; write myself in
t.Set(hash, "canonical-url", "dups", doc.url());
} / else this document already exists, ignore new copy
return t.Commit();
}
Figure 2: Example usage of the Percolator API to perform ba-
sic checksum clustering and eliminate documents with the same
content.
Time
no 3 —___»
[t]
Figure 3: Transactions under snapshot isolation perform reads
at a start timestamp (represented here by an open square) and
writes at a commit timestamp (closed circle). In this example,
transaction 2 would not see writes from transaction 1 since trans-
action 2’s start timestamp is before transaction 1’s commit times-
tamp. Transaction 3, however, will see writes from both 1 and 2.
Transaction 1 and 2 are running concurrently: if they both write
the same cell, at least one will abort.
tects against write-write conflicts: if transactions A and
B, running concurrently, write to the same cell, at most
one will commit. Snapshot isolation does not provide
serializability; in particular, transactions running under
snapshot isolation are subject to write skew [5]. The main
advantage of snapshot isolation over a serializable proto-
col is more efficient reads. Because any timestamp rep-
resents a consistent snapshot, reading a cell requires only
performing a Bigtable lookup at the given timestamp; ac-
quiring locks is not necessary. Figure 3 illustrates the re-
lationship between transactions under snapshot isolation.
Because it is built as a client library accessing
Bigtable, rather than controlling access to storage itself,
Percolator faces a different set of challenges implement-
ing distributed transactions than traditional PDBMSs.
Other parallel databases integrate locking into the sys-
tem component that manages access to the disk: since
each node already mediates access to data on the disk it
can grant locks on requests and deny accesses that violate
locking requirements.
By contrast, any node in Percolator can (and does) is-
sue requests to directly modify state in Bigtable: there is
no convenient place to intercept traffic and assign locks.
As a result, Percolator must explicitly maintain locks.
Locks must persist in the face of machine failure; if a
lock could disappear between the two phases of com-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) = 253
254
key bal:data bal:lock bal:write
6: 6: 6: data @ 5
ee 5: $10 5: 5:
Joe 6: 6: 6: data @ 5
5: $2 5: 5
1. Initial state: Joe’s account contains $2 dollars, Bob’s $10.
7:$3 7: 1am primary 7:
Bob 6: 6: 6: data @ 5
5: $10 a3 ae
Toe | 6: | 6: | 6: data @ 5
5: $2 5 a:
2. The transfer transaction begins by locking Bob’s account
balance by writing the lock column. This lock is the primary
for the transaction. The transaction also writes data at its start
timestamp, 7.
7: $3 7: Tam primary 7:
Bob 6: 6: 6: data @ 5
5: $10 3: 2:
7: $9 7: primary @ Bob.bal | 7:
Joe 6: 6: 6: data @ 5
5: $2 5: a
3. The transaction now locks Joe’s account and writes Joe’s new
balance (again, at the start timestamp). The lock is a secondary
for the transaction and contains a reference to the primary lock
(stored in row “Bob,” column “bal”); in case this lock is stranded
due to a crash, a transaction that wishes to clean up the lock
needs the location of the primary to synchronize the cleanup.
8: 8: 8: data @ 7
7: $3 ve i:
no 6: 6: 6: data @ 5
5: $10 a; 3s
7: $9 7: primary @ Bob.bal 7:
Joe 6: 6: 6:data @ 5
5: $2 3: 3:
4. The transaction has now reached the commit point: it erases
the primary lock and replaces it with a write record at a new
timestamp (called the commit timestamp): 8. The write record
contains a pointer to the timestamp where the data is stored.
Future readers of the column “bal” in row “Bob” will now see the
value $3.
8: 8: 8: data @ 7
7: $3 de 7:
Bot 6: 6: 6: data @ 5
5: $10 a a
8: 8: 8: data @ 7
Toe 7: $9 7: 7:
6: 6: 6: data @ 5
5:$2 a: 5:
5. The transaction completes by adding write records and
deleting locks at the secondary cells. In this case, there is only
one secondary: Joe.
Figure 4: This figure shows the Bigtable writes performed by
a Percolator transaction that mutates two rows. The transaction
transfers 7 dollars from Bob to Joe. Each Percolator column is
stored as 3 Bigtable columns: data, write metadata, and lock
metadata. Bigtable’s timestamp dimension is shown within each
cell; 12: “data” indicates that “data” has been written at Bigtable
timestamp 12. Newly written data is shown in boldface.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
Column Use
c:lock An uncommitted transaction is writing this
cell; contains the location of primary lock
c:write | Committed data present; stores the Bigtable
timestamp of the data
c:data Stores the data itself
c:notify Hint: observers may need to run
c:ack_O Observer “O” has run ; stores start timestamp
of successful last run
Figure 5: The columns in the Bigtable representation of a Per-
colator column named “c.”
mit, the system could mistakenly commit two transac-
tions that should have conflicted. The lock service must
provide high throughput; thousands of machines will be
requesting locks simultaneously. The lock service should
also be low-latency; each Get() operation requires read-
ing locks in addition to data, and we prefer to minimize
this latency. Given these requirements, the lock server
will need to be replicated (to survive failure), distributed
and balanced (to handle load), and write to a persistent
data store. Bigtable itself satisfies all of our requirements,
and so Percolator stores its locks in special in-memory
columns in the same Bigtable that stores data and reads
or modifies the locks in a Bigtable row transaction when
accessing data in that row.
We’ll now consider the transaction protocol in more
detail. Figure 6 shows the pseudocode for Percolator
transactions, and Figure 4 shows the layout of Percolator
data and metadata during the execution of a transaction.
These various metadata columns used by the system are
described in Figure 5. The transaction’s constructor asks
the timestamp oracle for a start timestamp (line 6), which
determines the consistent snapshot seen by Get(). Calls
to SetQ are buffered (line 7) until commit time. The ba-
sic approach for committing buffered writes is two-phase
commit, which is coordinated by the client. Transactions
on different machines interact through row transactions
on Bigtable tablet servers.
In the first phase of commit (“prewrite’’), we try to
lock all the cells being written. (To handle client failure,
we designate one lock arbitrarily as the primary; we’ll
discuss this mechanism below.) The transaction reads
metadata to check for conflicts in each cell being writ-
ten. There are two kinds of conflicting metadata: if the
transaction sees another write record after its start times-
tamp, it aborts (line 32); this is the write-write conflict
that snapshot isolation guards against. If the transaction
sees another lock at any timestamp, it also aborts (line
34). It’s possible that the other transaction is just being
slow to release its lock after having already committed
below our start timestamp, but we consider this unlikely,
so we abort. If there is no conflict, we write the lock and
USENIX Association
25
26 // Prewrite tries to lock cell w, returning false in case of conflict.
class Transaction {
struct Write { Row row; Column col; string value; };
vector<Write> writes_}
int start_ts_;
Transaction() : start_ts_(oracle.GetTimestamp()) {}
void Set(Write w) { writes_.push_back(w); }
bool Get(Row row, Column c, string* value) {
while (true) {
bigtable::Txn T = bigtable::StartRowTransaction(row);
// Check for locks that signal concurrent writes.
if (T.Read(row, c+"lock", [0, start_ts_])) {
// There is a pending lock; try to clean it and wait
BackoffAndMaybeCleanupLock(row, c);
continue;
}
// Find the latest write below our start_timestamp.
latest_write = T.Read(row, c+"write", [0, start_ts_]);
if (!latest_write.found()) return false; // no data
int data_ts = latest_write.start_timestamp();
*value = T.Read(row, c+"data", [data_ts, data_ts]);
return true;
}
27 bool Prewrite(Write w, Write primary) {
28 Column c = w.col;
29 bigtable::Txn T = bigtable::StartRowTransaction(w.row);
30
31 // Abort on writes after our start timestamp ...
32 if (T.Read(w.row, c+"write", [start_ts_, oo])) return false;
33 // ... or locks at any timestamp.
34 if (T.Read(w.row, c+"lock", [0, co])) return false;
35
36 T.Write(w.row, c+"data", start_ts_, w.value);
37 T.Write(w.row, c+"lock", start_ts_,
38 {primary.row, primary.col}); // The primary’s location.
39 return T.Commit();
40
41 bool Commit() {
42 Write primary = writes_[0];
43 vector<Write> secondaries(writes_.begin()+1, writes_.end());
44 if (!Prewrite(primary, primary)) return false;
45 for (Write w : secondaries)
46 if (!Prewrite(w, primary)) return false;
47
48 int commit_ts = oracle_.GetTimestamp();
49
50 // Commit primary first.
51 Write p = primary;
52 bigtable::Txn T = bigtable::StartRowTransaction(p.row);
53 if (!T.Read(p.row, p.colt"lock", [start_ts_, start_ts_]))
54 return false; = // aborted while working
55 T.Write(p.row, p.col+"write", commit_ts,
56 start_ts_); // Pointer to data written at start_ts_.
57 T.Erase(p.row, p.colt"lock", commit-_ts);
58 if ('‘T.-Commit()) return false; // commit point
59
60 // Second phase: write out write records for secondary cells.
61 for (Write w : secondaries) {
62 bigtable::Write(w.row, w.col+"write", commit_ts, start_ts_);
63 bigtable::Erase(w.row, w.colt"lock", commit_ts);
64}
65 return true;
66
67 } YW class Transaction
Figure 6: Pseudocode for Percolator transaction protocol.
USENIX Association
the data to each cell at the start timestamp (lines 36-38).
If no cells conflict, the transaction may commit and
proceeds to the second phase. At the beginning of the
second phase, the client obtains the commit timestamp
from the timestamp oracle (line 48). Then, at each cell
(starting with the primary), the client releases its lock and
make its write visible to readers by replacing the lock
with a write record. The write record indicates to read-
ers that committed data exists in this cell; it contains a
pointer to the start timestamp where readers can find the
actual data. Once the primary’s write is visible (line 58),
the transaction must commit since it has made a write
visible to readers.
A Get() operation first checks for a lock in the times-
tamp range [0, start_timestamp], which is the range of
timestamps visible in the transaction’s snapshot (line 12).
If a lock is present, another transaction is concurrently
writing this cell, so the reading transaction must wait un-
til the lock is released. If no conflicting lock is found,
Get() reads the latest write record in that timestamp range
(line 19) and returns the data item corresponding to that
write record (line 22).
Transaction processing is complicated by the possibil-
ity of client failure (tablet server failure does not affect
the system since Bigtable guarantees that written locks
persist across tablet server failures). If a client fails while
a transaction is being committed, locks will be left be-
hind. Percolator must clean up those locks or they will
cause future transactions to hang indefinitely. Percolator
takes a lazy approach to cleanup: when a transaction A
encounters a conflicting lock left behind by transaction
B, A may determine that B has failed and erase its locks.
It is very difficult for A to be perfectly confident in
its judgment that B is failed; as a result we must avoid
a race between A cleaning up B’s transaction and a not-
actually-failed B committing the same transaction. Per-
colator handles this by designating one cell in every
transaction as a synchronizing point for any commit or
cleanup operations. This cell’s lock is called the primary
lock. Both A and B agree on which lock is primary (the
location of the primary is written into the locks at all
other cells). Performing either a cleanup or commit op-
eration requires modifying the primary lock; since this
modification is performed under a Bigtable row transac-
tion, only one of the cleanup or commit operations will
succeed. Specifically: before B commits, it must check
that it still holds the primary lock and replace it with a
write record. Before A erases B’s lock, A must check
the primary to ensure that B has not committed; if the
primary lock is still present, then it can safely erase the
lock.
When a client crashes during the second phase of
commit, a transaction will be past the commit point
(it has written at least one write record) but will still
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) = 255
256
have locks outstanding. We must perform roll-forward on
these transactions. A transaction that encounters a lock
can distinguish between the two cases by inspecting the
primary lock: if the primary lock has been replaced by a
write record, the transaction which wrote the lock must
have committed and the lock must be rolled forward, oth-
erwise it should be rolled back (since we always commit
the primary first, we can be sure that it is safe to roll back
if the primary is not committed). To roll forward, the
transaction performing the cleanup replaces the stranded
lock with a write record as the original transaction would
have done.
Since cleanup is synchronized on the primary lock, it
is safe to clean up locks held by live clients; however,
this incurs a performance penalty since rollback forces
the transaction to abort. So, a transaction will not clean
up a lock unless it suspects that a lock belongs to a dead
or stuck worker. Percolator uses simple mechanisms to
determine the liveness of another transaction. Running
workers write a token into the Chubby lockservice [8]
to indicate they belong to the system; other workers can
use the existence of this token as a sign that the worker is
alive (the token is automatically deleted when the process
exits). To handle a worker that is live, but not working,
we additionally write the wall time into the lock; a lock
that contains a too-old wall time will be cleaned up even
if the worker’s liveness token is valid. To handle long-
running commit operations, workers periodically update
this wall time while committing.
2.3. Timestamps
The timestamp oracle is a server that hands out times-
tamps in strictly increasing order. Since every transaction
requires contacting the timestamp oracle twice, this ser-
vice must scale well. The oracle periodically allocates
a range of timestamps by writing the highest allocated
timestamp to stable storage; given an allocated range of
timestamps, the oracle can satisfy future requests strictly
from memory. If the oracle restarts, the timestamps will
jump forward to the maximum allocated timestamp (but
will never go backwards). To save RPC overhead (at the
cost of increasing transaction latency) each Percolator
worker batches timestamp requests across transactions
by maintaining only one pending RPC to the oracle. As
the oracle becomes more loaded, the batching naturally
increases to compensate. Batching increases the scalabil-
ity of the oracle but does not affect the timestamp guar-
antees. Our oracle serves around 2 million timestamps
per second from a single machine.
The transaction protocol uses strictly increasing times-
tamps to guarantee that Get() returns all committed
writes before the transaction’s start timestamp. To see
how it provides this guarantee, consider a transaction R
reading at timestamp Tp and a transaction W that com-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
mitted at timestamp Ty < T'p; we will show that R sees
W’s writes. Since Tw < Tr, we know that the times-
tamp oracle gave out Ty before or in the same batch
as TR; hence, W requested Ti before R received Tp.
We know that R can’t do reads before receiving its start
timestamp T'p and that W wrote locks before requesting
its commit timestamp Ty. Therefore, the above property
guarantees that W must have at least written all its locks
before R did any reads; R’s Get() will see either the fully-
committed write record or the lock, in which case W will
block until the lock is released. Either way, W’s write is
visible to R’s Get().
2.4 Notifications
Transactions let the user mutate the table while main-
taining invariants, but users also need a way to trigger
and run the transactions. In Percolator, the user writes
code (“observers’’) to be triggered by changes to the ta-
ble, and we link all the observers into a binary running
alongside every tablet server in the system. Each ob-
server registers a function and a set of columns with Per-
colator, and Percolator invokes the function after data is
written to one of those columns in any row.
Percolator applications are structured as a series of ob-
servers; each observer completes a task and creates more
work for “downstream” observers by writing to the table.
In our indexing system, a MapReduce loads crawled doc-
uments into Percolator by running loader transactions,
which trigger the document processor transaction to in-
dex the document (parse, extract links, etc.). The docu-
ment processor transaction triggers further transactions
like clustering. The clustering transaction, in turn, trig-
gers transactions to export changed document clusters to
the serving system.
Notifications are similar to database triggers or events
in active databases [29], but unlike database triggers,
they cannot be used to maintain database invariants. In
particular, the triggered observer runs in a separate trans-
action from the triggering write, so the triggering write
and the triggered observer’s writes are not atomic. No-
tifications are intended to help structure an incremental
computation rather than to help maintain data integrity.
This difference in semantics and intent makes observer
behavior much easier to understand than the complex se-
mantics of overlapping triggers. Percolator applications
consist of very few observers — the Google indexing
system has roughly 10 observers. Each observer is ex-
plicitly constructed in the main() of the worker binary,
so it is clear what observers are active. It is possible for
several observers to observe the same column, but we
avoid this feature so it is clear what observer will run
when a particular column is written. Users do need to be
wary about infinite cycles of notifications, but Percolator
does nothing to prevent this; the user typically constructs
USENIX Association
USENIX Association
a series of observers to avoid infinite cycles.
We do provide one guarantee: at most one observer’s
transaction will commit for each change of an observed
column. The converse is not true, however: multiple
writes to an observed column may cause the correspond-
ing observer to be invoked only once. We call this feature
message collapsing, since it helps avoid computation by
amortizing the cost of responding to many notifications.
For example, it is sufficient for http: //google.com
to be reprocessed periodically rather than every time we
discover a new link pointing to it.
To provide these semantics for notifications, each ob-
served column has an accompanying “acknowledgment”
column for each observer, containing the latest start
timestamp at which the observer ran. When the observed
column is written, Percolator starts a transaction to pro-
cess the notification. The transaction reads the observed
column and its corresponding acknowledgment column.
If the observed column was written after its last acknowl-
edgment, then we run the observer and set the acknowl-
edgment column to our start timestamp. Otherwise, the
observer has already been run, so we do not run it again.
Note that if Percolator accidentally starts two transac-
tions concurrently for a particular notification, they will
both see the dirty notification and run the observer, but
one will abort because they will conflict on the acknowl-
edgment column. We promise that at most one observer
will commit for each notification.
To implement notifications, Percolator needs to effi-
ciently find dirty cells with observers that need to be run.
This search is complicated by the fact that notifications
are rare: our table has trillions of cells, but, if the system
is keeping up with applied load, there will only be mil-
lions of notifications. Additionally, observer code is run
on a large number of client processes distributed across a
collection of machines, meaning that this search for dirty
cells must be distributed.
To identify dirty cells, Percolator maintains a special
“notify” Bigtable column, containing an entry for each
dirty cell. When a transaction writes an observed cell,
it also sets the corresponding notify cell. The workers
perform a distributed scan over the notify column to find
dirty cells. After the observer is triggered and the transac-
tion commits, we remove the notify cell. Since the notify
column is just a Bigtable column, not a Percolator col-
umn, it has no transactional properties and serves only as
a hint to the scanner to check the acknowledgment col-
umn to determine if the observer should be run.
To make this scan efficient, Percolator stores the notify
column in a separate Bigtable locality group so that scan-
ning over the column requires reading only the millions
of dirty cells rather than the trillions of total data cells.
Each Percolator worker dedicates several threads to the
scan. For each thread, the worker chooses a portion of the
table to scan by first picking a random Bigtable tablet,
then picking a random key in the tablet, and finally scan-
ning the table from that position. Since each worker is
scanning a random region of the table, we worry about
two workers running observers on the same row con-
currently. While this behavior will not cause correctness
problems due to the transactional nature of notifications,
it is inefficient. To avoid this, each worker acquires a lock
from a lightweight lock service before scanning the row.
This lock server need not persist state since it is advisory
and thus is very scalable.
The random-scanning approach requires one addi-
tional tweak: when it was first deployed we noticed that
scanning threads would tend to clump together in a few
regions of the table, effectively reducing the parallelism
of the scan. This phenomenon is commonly seen in pub-
lic transportation systems where it is known as “platoon-
ing” or “bus clumping” and occurs when a bus is slowed
down (perhaps by traffic or slow loading). Since the num-
ber of passengers at each stop grows with time, loading
delays become even worse, further slowing the bus. Si-
multaneously, any bus behind the slow bus speeds up
as it needs to load fewer passengers at each stop. The
result is a clump of buses arriving simultaneously at a
stop [19]. Our scanning threads behaved analogously: a
thread that was running observers slowed down while
threads “behind” it quickly skipped past the now-clean
rows to clump with the lead thread and failed to pass
the lead thread because the clump of threads overloaded
tablet servers. To solve this problem, we modified our
system in a way that public transportation systems can-
not: when a scanning thread discovers that it is scanning
the same row as another thread, it chooses a new random
location in the table to scan. To further the transporta-
tion analogy, the buses (scanner threads) in our city avoid
clumping by teleporting themselves to a random stop (lo-
cation in the table) if they get too close to the bus in front
of them.
Finally, experience with notifications led us to intro-
duce a lighter-weight but semantically weaker notifica-
tion mechanism. We found that when many duplicates of
the same page were processed concurrently, each trans-
action would conflict trying to trigger reprocessing of the
same duplicate cluster. This led us to devise a way to no-
tify a cell without the possibility of transactional conflict.
We implement this weak notification by writing only to
the Bigtable “notify” column. To preserve the transac-
tional semantics of the rest of Percolator, we restrict these
weak notifications to a special type of column that can-
not be written, only notified. The weaker semantics also
mean that multiple observers may run and commit as a
result of a single weak notification (though the system
tries to minimize this occurrence). This has become an
important feature for managing conflicts; if an observer
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 710) = 257
258
frequently conflicts on a hotspot, it often helps to break
it into two observers connected by a non-transactional
notification on the hotspot.
2.5 Discussion
One of the inefficiencies of Percolator relative to a
MapReduce-based system is the number of RPCs sent
per work-unit. While MapReduce does a single large
read to GFS and obtains all of the data for 10s or 100s
of web pages, Percolator performs around 50 individual
Bigtable operations to process a single document.
One source of additional RPCs occurs during commit.
When writing a lock, we must do a read-modify-write
operation requiring two Bigtable RPCs: one to read for
conflicting locks or writes and another to write the new
lock. To reduce this overhead, we modified the Bigtable
API by adding conditional mutations which implements
the read-modify-write step in a single RPC. Many con-
ditional mutations destined for the same tablet server
can also be batched together into a single RPC to fur-
ther reduce the total number of RPCs we send. We create
batches by delaying lock operations for several seconds
to collect them into batches. Because locks are acquired
in parallel, this adds only a few seconds to the latency
of each transaction; we compensate for the additional la-
tency with greater parallelism. Batching also increases
the time window in which conflicts may occur, but in our
low-contention environment this has not proved to be a
problem.
We also perform the same batching when reading from
the table: every read operation is delayed to give it a
chance to form a batch with other reads to the same
tablet server. This delays each read, potentially greatly
increasing transaction latency. A final optimization miti-
gates this effect, however: prefetching. Prefetching takes
advantage of the fact that reading two or more values
in the same row is essentially the same cost as reading
one value. In either case, Bigtable must read the entire
SSTable block from the file system and decompress it.
Percolator attempts to predict, each time a column is
read, what other columns in a row will be read later in
the transaction. This prediction is made based on past be-
havior. Prefetching, combined with a cache of items that
have already been read, reduces the number of Bigtable
reads the system would otherwise do by a factor of 10.
Early in the implementation of Percolator, we decided
to make all API calls blocking and rely on running thou-
sands of threads per machine to provide enough par-
allelism to maintain good CPU utilization. We chose
this thread-per-request model mainly to make application
code easier to write, compared to the event-driven model.
Forcing users to bundle up their state each of the (many)
times they fetched a data item from the table would have
made application development much more difficult. Our
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
experience with thread-per-request was, on the whole,
positive: application code is simple, we achieve good uti-
lization on many-core machines, and crash debugging is
simplified by meaningful and complete stack traces. We
encountered fewer race conditions in application code
than we feared. The biggest drawbacks of the approach
were scalability issues in the Linux kernel and Google
infrastructure related to high thread counts. Our in-house
kernel development team was able to deploy fixes to ad-
dress the kernel issues.
3 Evaluation
Percolator lies somewhere in the performance space
between MapReduce and DBMSs. For example, because
Percolator is a distributed system, it uses far more re-
sources to process a fixed amount of data than a tradi-
tional DBMS would; this is the cost of its scalability.
Compared to MapReduce, Percolator can process data
with far lower latency, but again, at the cost of additional
resources required to support random lookups. These are
engineering tradeoffs which are difficult to quantify: how
much of an efficiency loss is too much to pay for the abil-
ity to add capacity endlessly simply by purchasing more
machines? Or: how does one trade off the reduction in
development time provided by a layered system against
the corresponding decrease in efficiency?
In this section we attempt to answer some of these
questions by first comparing Percolator to batch pro-
cessing systems via our experiences with converting
a MapReduce-based indexing pipeline to use Percola-
tor. We'll also evaluate Percolator with microbench-
marks and a synthetic workload based on the well-known
TPC-E benchmark [1]; this test will give us a chance to
evaluate the scalability and efficiency of Percolator rela-
tive to Bigtable and DBMSs.
All of the experiments in this section are run on a sub-
set of the servers in a Google data center. The servers run
the Linux operating system on x86 processors; each ma-
chine is connected to several commodity SATA drives.
3.1. Converting from MapReduce
We built Percolator to create Google’s large “base”
index, a task previously performed by MapReduce. In
our previous system, each day we crawled several billion
documents and fed them along with a repository of ex-
isting documents through a series of 100 MapReduces.
The result was an index which answered user queries.
Though not all 100 MapReduces were on the critical path
for every document, the organization of the system as a
series of MapReduces meant that each document spent
2-3 days being indexed before it could be returned as a
search result.
The Percolator-based indexing system (known as Caf-
feine [25]), crawls the same number of documents,
USENIX Association
USENIX Association
but we feed each document through Percolator as it
is crawled. The immediate advantage, and main design
goal, of Caffeine is a reduction in latency: the median
document moves through Caffeine over 100x faster than
the previous system. This latency improvement grows as
the system becomes more complex: adding a new clus-
tering phase to the Percolator-based system requires an
extra lookup for each document rather an extra scan over
the repository. Additional clustering phases can also be
implemented in the same transaction rather than in an-
other MapReduce; this simplification is one reason the
number of observers in Caffeine (10) is far smaller than
the number of MapReduces in the previous system (100).
This organization also allows for the possibility of per-
forming additional processing on only a subset of the
repository without rescanning the entire repository.
Adding additional clustering phases isn’t free in an in-
cremental system: more resources are required to make
sure the system keeps up with the input, but this is still
an improvement over batch processing systems where no
amount of resources can overcome delays introduced by
stragglers in an additional pass over the repository. Caf-
feine is essentially immune to stragglers that were a seri-
ous problem in our batch-based indexing system because
the bulk of the processing does not get held up by a few
very slow operations. The radically-lower latency of the
new system also enables us to remove the rigid distinc-
tions between large, slow-to-update indexes and smaller,
more rapidly updated indexes. Because Percolator frees
us from needing to process the repository each time we
index documents, we can also make it larger: Caffeine’s
document collection is currently 3x larger than the previ-
ous system’s and is limited only by available disk space.
Compared to the system it replaced, Caffeine uses
roughly twice as many resources to process the same
crawl rate. However, Caffeine makes good use of the ex-
tra resources. If we were to run the old indexing system
with twice as many resources, we could either increase
the index size or reduce latency by at most a factor of two
(but not do both). On the other hand, if Caffeine were run
with half the resources, it would not be able to process as
many documents per day as the old system (but the doc-
uments it did produce would have much lower latency).
The new system is also easier to operate. Caffeine has
far fewer moving parts: we run tablet servers, Percola-
tor workers, and chunkservers. In the old system, each of
a hundred different MapReduces needed to be individ-
ually configured and could independently fail. Also, the
“peaky” nature of the MapReduce workload made it hard
to fully utilize the resources of a datacenter compared to
Percolator’s much smoother resource usage.
The simplicity of writing straight-line code and the
ability to do random lookups into the repository makes
developing new features for Percolator easy. Under
—— Mapreduce
2500 —-®— Percolator
e
& 2000
a
5
=
£
S
es 1500
&
=
2
3 1000
o
500 ;
0+ ~~ - este $ =—-= ; ,
10% 20% 30% 40% 50%
Crawl rate (Percentage of repository updated per hour)
Figure 7: Median document clustering delay for Percolator
(dashed line) and MapReduce (solid line). For MapReduce, all
documents finish processing at the same time and error bars
represent the min, median, and max of three runs of the clus-
tering MapReduce. For Percolator, we are able to measure the
delay of individual documents, so the error bars represent the
5th- and 95th-percentile delay on a per-document level.
MapReduce, random lookups are awkward and costly.
On the other hand, Caffeine developers need to reason
about concurrency where it did not exist in the MapRe-
duce paradigm. Transactions help deal with this concur-
rency, but can’t fully eliminate the added complexity.
To quantify the benefits of moving from MapRe-
duce to Percolator, we created a synthetic benchmark
that clusters newly crawled documents against a billion-
document repository to remove duplicates in much the
same way Google’s indexing pipeline operates. Docu-
ments are clustered by three clustering keys. In a real sys-
tem, the clustering keys would be properties of the docu-
ment like redirect target or content hash, but in this exper-
iment we selected them uniformly at random from a col-
lection of 750M possible keys. The average cluster in our
synthetic repository contains 3.3 documents, and 93% of
the documents are in a non-singleton cluster. This dis-
tribution of keys exercises the clustering logic, but does
not expose it to the few extremely large clusters we have
seen in practice. These clusters only affect the latency
tail and not the results we present here. In the Percola-
tor clustering implementation, each crawled document is
immediately written to the repository to be clustered by
an observer. The observer maintains an index table for
each clustering key and compares the document against
each index to determine if it is a duplicate (an elabora-
tion of Figure 2). MapReduce implements clustering of
continually arriving documents by repeatedly running a
sequence of three clustering MapReduces (one for each
clustering key). The sequence of three MapReduces pro-
cesses the entire repository and any crawled documents
that accumulated while the previous three were running.
This experiment simulates clustering documents
crawled at a uniform rate. Whether MapReduce or Perco-
lator performs better under this metric is a function of the
how frequently documents are crawled (the crawl rate)
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) 259
260
and the repository size. We explore this space by fixing
the size of the repository and varying the rate at which
new documents arrive, expressed as a percentage of the
repository crawled per hour. In a practical system, a very
small percentage of the repository would be crawled per
hour: there are over | trillion web pages on the web (and
ideally in an indexing system’s repository), far too many
to crawl a reasonable fraction of in a single day. When
the new input is a small fraction of the repository (low
crawl rate), we expect Percolator to outperform MapRe-
duce since MapReduce must map over the (large) repos-
itory to cluster the (small) batch of new documents while
Percolator does work proportional only to the small batch
of newly arrived documents (a lookup in up to three in-
dex tables per document). At very large crawl rates where
the number of newly crawled documents approaches the
size of the repository, MapReduce will perform better
than Percolator. This cross-over occurs because stream-
ing data from disk is much cheaper, per byte, than per-
forming random lookups. At the cross-over the total cost
of the lookups required to cluster the new documents un-
der Percolator equals the cost to stream the documents
and the repository through MapReduce. At crawl rates
higher than that, one is better off using MapReduce.
We ran this benchmark on 240 machines and measured
the median delay between when a document is crawled
and when it is clustered. Figure 7 plots the median la-
tency of document processing for both implementations
as a function of crawl rate. When the crawl rate is low,
Percolator clusters documents faster than MapReduce as
expected; this scenario is illustrated by the leftmost pair
of points which correspond to crawling | percent of doc-
uments per hour. MapReduce requires approximately 20
minutes to cluster the documents because it takes 20
minutes just to process the repository through the three
MapReduces (the effect of the few newly crawled doc-
uments on the runtime is negligible). This results in an
average delay between crawling a document and cluster-
ing of around 30 minutes: a random document waits 10
minutes after being crawled for the previous sequence of
MapReduces to finish and then spends 20 minutes be-
ing processed by the three MapReduces. Percolator, on
the other hand, finds a newly loaded document and pro-
cesses it in two seconds on average, or about 1000x faster
than MapReduce. The two seconds includes the time to
find the dirty notification and run the transaction that per-
forms the clustering. Note that this 1000x latency im-
provement could be made arbitrarily large by increasing
the size of the repository.
As the crawl rate increases, MapReduce’s processing
time grows correspondingly. Ideally, it would be propor-
tional to the combined size of the repository and the input
which grows with the crawl rate. In practice, the running
time of a small MapReduce like this is limited by strag-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
Bigtable Percolator | Relative
Read/s 15513 14590 0.94
Write/s 31003 7232 0.23
Figure 8: The overhead of Percolator operations relative to
Bigtable. Write overhead is due to additional operations Percola-
tor needs to check for conflicts.
glers, so the growth in processing time (and thus cluster-
ing latency) is only weakly correlated to crawl rate at low
crawl rates. The 6 percent crawl rate, for example, only
adds 150GB to a 1TB data set; the extra time to process
150GB is in the noise. The latency of Percolator is rela-
tively unchanged as the craw] rate grows until it suddenly
increases to effectively infinity at a crawl rate of 40%
per hour. At this point, Percolator saturates the resources
of the test cluster, is no longer able to keep up with the
crawl rate, and begins building an unbounded queue of
unprocessed documents. The dotted asymptote at 40%
is an extrapolation of Percolator’s performance beyond
this breaking point. MapReduce is subject to the same
effect: eventually crawled documents accumulate faster
than MapReduce is able to cluster them, and the batch
size will grow without bound in subsequent runs. In this
particular configuration, however, MapReduce can sus-
tain craw] rates in excess of 100% (the dotted line, again,
extrapolates performance).
These results show that Percolator can process docu-
ments at orders of magnitude better latency than MapRe-
duce in the regime where we expect real systems to op-
erate (single-digit craw] rates).
3.2 Microbenchmarks
In this section, we determine the cost of the trans-
actional semantics provided by Percolator. In these ex-
periments, we compare Percolator to a “raw” Bigtable.
We are only interested in the relative performance
of Bigtable and Percolator since any improvement in
Bigtable performance will translate directly into an im-
provement in Percolator performance. Figure 8 shows
the performance of Percolator and raw Bigtable running
against a single tablet server. All data was in the tablet
server’s cache during the experiments and Percolator’s
batching optimizations were disabled.
As expected, Percolator introduces overhead relative
to Bigtable. We first measure the number of random
writes that the two systems can perform. In the case of
Percolator, we execute transactions that write a single
cell and then commit; this represents the worst case for
Percolator overhead. When doing a write, Percolator in-
curs roughly a factor of four overhead on this benchmark.
This is the result of the extra operations Percolator re-
quires for commit beyond the single write that Bigtable
issues: a read to check for locks, a write to add the lock,
and a second write to remove the lock record. The read,
in particular, is more expensive than a write and accounts
USENIX Association
USENIX Association
for most of the overhead. In this test, the limiting fac-
tor was the performance of the tablet server, so the addi-
tional overhead of fetching timestamps is not measured.
We also tested random reads: Percolator performs a sin-
gle Bigtable operation per read, but that read operation
is somewhat more complex than the raw Bigtable oper-
ation (the Percolator read looks at metadata columns in
addition to data columns).
3.3. Synthetic Workload
To evaluate Percolator on a more realistic work-
load, we implemented a synthetic benchmark based on
TPC-E [1]. This isn’t the ideal benchmark for Percola-
tor since TPC-E is designed for OLTP systems, and a
number of Percolator’s tradeoffs impact desirable prop-
erties of OLTP systems (the latency of conflicting trans-
actions, for example). TPC-E is a widely recognized and
understood benchmark, however, and it allows us to un-
derstand the cost of our system against more traditional
databases.
TPC-E simulates a brokerage firm with customers who
perform trades, market search, and account inquiries.
The brokerage submits trade orders to a market ex-
change, which executes the trade and updates broker and
customer state. The benchmark measures the number of
trades executed. On average, each customer performs a
trade once every 500 seconds, so the benchmark scales
by adding customers and associated data.
TPC-E traditionally has three components — a cus-
tomer emulator, a market emulator, and a DBMS run-
ning stored SQL procedures. Since Percolator is a client
library running against Bigtable, our implementation is
a combined customer/market emulator that calls into
the Percolator library to perform operations against
Bigtable. Percolator provides a low-level Get/Set/iterator
API rather than a high-level SQL interface, so we created
indexes and did all the ‘query planning’ by hand.
Since Percolator is an incremental processing system
rather than an OLTP system, we don’t attempt to meet the
TPC-E latency targets. Our average transaction latency is
2 to 5 seconds, but outliers can take several minutes. Out-
liers are caused by, for example, exponential backoff on
conflicts and Bigtable tablet unavailability. Finally, we
made a small modification to the TPC-E transactions. In
TPC-E, each trade result increases the broker’s commis-
sion and increments his trade count. Each broker services
a hundred customers, so the average broker must be up-
dated once every 5 seconds, which causes repeated write
conflicts in Percolator. In Percolator, we would imple-
ment this feature by writing the increment to a side table
and periodically aggregating each broker’s increments;
for the benchmark, we choose to simply omit this write.
Figure 9 shows how the resource usage of Percolator
scales as demand increases. We will measure resource
TPS
T T 1
0 5000 10000 15000
cores
Figure 9: Transaction rate on a TPC-E-like benchmark as a func-
tion of cores used. The dotted line shows linear scaling.
usage in CPU cores since that is the limiting resource
in our experimental environment. We were able to pro-
cure a small number of machines for testing, but our
test Bigtable cell shares the disk resources of a much
larger production cluster. As a result, disk bandwidth
is not a factor in the system’s performance. In this ex-
periment, we configured the benchmark with increasing
numbers of customers and measured both the achieved
performance and the number of cores used by all parts
of the system including cores used for background main-
tenance such as Bigtable compactions. The relationship
between performance and resource usage is essentially
linear across several orders of magnitude, from 11 cores
to 15,000 cores.
This experiment also provides an opportunity to mea-
sure the overheads in Percolator relative to a DBMS.
The fastest commercial TPC-E system today performs
3,183 tpsE using a single large shared-memory machine
with 64 Intel Nehalem cores with 2 hyperthreads per
core [33]. Our synthetic benchmark based on TPC-E per-
forms 11,200 tps using 15,000 cores. This comparison
is very rough: the Nehalem cores in the comparison ma-
chine are significantly faster than the cores in our test cell
(small-scale testing on Nehalem processors shows that
they are 20-30% faster per-thread compared to the cores
in the test cluster). However, we estimate that Percola-
tor uses roughly 30 times more CPU per transaction than
the benchmark system. On a cost-per-transaction basis,
the gap is likely much less than 30 since our test clus-
ter uses cheaper, commodity hardware compared to the
enterprise-class hardware in the reference machine.
The conventional wisdom on implementing databases
is to “get close to the iron” and use hardware as directly
as possible since even operating system structures like
disk caches and schedulers make it hard to implement
an efficient database [32]. In Percolator we not only in-
terposed an operating system between our database and
the hardware, but also several layers of software and net-
work links. The conventional wisdom is correct: this ar-
rangement has a cost. There are substantial overheads in
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI’10) 261
262
60.0 T T T T T T
50.0
40.0
30.0 + 4
20.0 + 4
Transactions per Second
10.0 - 4
TradeResult (tps)
0.0 peg egg eg Jig yg ng fp pee pg Jey gg jhe at big
16:20 16:40 17:00 17:20 17:40 18:0
Figure 10: Recovery of tps after 33% tablet server mortality
preparing requests to go on the wire, sending them, and
processing them on a remote machine. To illustrate these
overheads in Percolator, consider the act of mutating the
database. In a DBMS, this incurs a function call to store
the data in memory and a system call to force the log to
hardware controlled RAID array. In Percolator, a client
performing a transaction commit sends multiple RPCs
to Bigtable, which commits the mutation by logging it
to 3 chunkservers, which make system calls to actually
flush the data to disk. Later, that same data will be com-
pacted into minor and major sstables, each of which will
be again replicated to multiple chunkservers.
The CPU inflation factor is the cost of our layering.
In exchange, we get scalability (our fastest result, though
not directly comparable to TPC-E, is more than 3x the
current official record [33]), and we inherit the useful
features of the systems we build upon, like resilience to
failures. To demonstrate the latter, we ran the benchmark
with 15 tablet servers and allowed the performance to
stabilize. Figure 10 shows the performance of the system
over time. The dip in performance at 17:09 corresponds
to a failure event: we killed a third of the tablet servers.
Performance drops immediately after the failure event
but recovers as the tablets are reloaded by other tablet
servers. We allowed the killed tablet servers to restart so
performance eventually returns to the original level.
4 Related Work
Batch processing systems like MapReduce [13, 22,
24] are well suited for efficiently transforming or analyz-
ing an entire corpus: these systems can simultaneously
use a large number of machines to process huge amounts
of data quickly. Despite this scalability, re-running a
MapReduce pipeline on each small batch of updates re-
sults in unacceptable latency and wasted work. Over-
lapping or pipelining the adjacent stages can reduce la-
tency [10], but straggler shards still set the minimum
time to complete the pipeline. Percolator avoids the ex-
pense of repeated scans by, essentially, creating indexes
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
on the keys used to cluster documents; one of criticisms
leveled by Stonebraker and DeWitt in their initial critique
of MapReduce [16] was that MapReduce did not support
such indexes.
Several proposed modifications to MapReduce [18,
26, 35] reduce the cost of processing changes to a reposi-
tory by allowing workers to randomly read a base repos-
itory while mapping over only newly arrived work. To
implement clustering in these systems, we would likely
maintain a repository per clustering phase. Avoiding the
need to re-map the entire repository would allow us to
make batches smaller, reducing latency. DryadInc [31]
attacks the same problem by reusing identical portions
of the computation from previous runs and allowing the
user to specify a merge function that combines new in-
put with previous iterations’ outputs. These systems rep-
resent a middle-ground between mapping over the en-
tire repository using MapReduce and processing a single
document at a time with Percolator.
Databases satisfy many of the requirements of an
incremental system: a RDBMS can make many inde-
pendent and concurrent changes to a large corpus and
provides a flexible language for expressing computa-
tion (SQL). In fact, Percolator presents the user with
a database-like interface: it supports transactions, itera-
tors, and secondary indexes. While Percolator provides
distributed transactions, it is by no means a full-fledged
DBMS: it lacks a query language, for example, as well
as full relational operations such as join. Percolator is
also designed to operate at much larger scales than exist-
ing parallel databases and to deal better with failed ma-
chines. Unlike Percolator, database systems tend to em-
phasize latency over throughput since a human is often
waiting for the results of a database query.
The organization of data in Percolator mirrors that
of shared-nothing parallel databases [7, 15, 4]. Data is
distributed across a number of commodity machines in
shared-nothing fashion: the machines communicate only
via explicit RPCs; no shared memory or shared disks are
used. Data stored by Percolator is partitioned by Bigtable
into tablets of contiguous rows which are distributed
among machines; this mirrors the declustering performed
by parallel databases.
The transaction management of Percolator builds on a
long line of work on distributed transactions for database
systems. Percolator implements snapshot isolation [5] by
extending multi-version timestamp ordering [6] across a
distributed system using two-phase commit.
An analogy can be drawn between the role of ob-
servers in Percolator to incrementally move the system
towards a “clean” state and the incremental maintenance
of materialized views in traditional databases (see Gupta
and Mumick [21] for a survey of the field). In practice,
while some indexing tasks like clustering documents by
USENIX Association
USENIX Association
contents could be expressed in a form appropriate for in-
cremental view maintenance it would likely be hard to
express the transformation of a raw document into an in-
dexed document in such a form.
The utility of parallel databases and, by extension,
a system like Percolator, has been questioned several
times [17] over their history. Hardware trends have, in
the past, worked against parallel databases. CPUs have
become so much faster than disks that a few CPUs in
a shared-memory machine can drive enough disk heads
to service required loads without the complexity of dis-
tributed transactions: the top TPC-E benchmark results
today are achieved on large shared-memory machines
connected to a SAN. This trend is beginning to reverse
itself, however, as the enormous datasets like those Per-
colator is intended to process become far too large for a
single shared-memory machine to handle. These datasets
require a distributed solution that can scale to 1000s of
machines, while existing parallel databases can utilize
only 100s of machines [30]. Percolator provides a sys-
tem that is scalable enough for Internet-sized datasets by
sacrificing some (but not all) of the flexibility and low-
latency of parallel databases.
Distributed storage systems like Bigtable have the
scalability and fault-tolerance properties of MapReduce
but provide a more natural abstraction for storing a repos-
itory. Using a distributed storage system allows for low-
latency updates since the system can change state by mu-
tating the repository rather than rewriting it. However,
Percolator is a data transformation system, not only a
data storage system: it provides a way to structure com-
putation to transform that data. In contrast, systems like
Dynamo [14], Bigtable, and PNUTS [11] provide highly
available data storage without the attendant mechanisms
of transformation. These systems can also be grouped
with the NoSQL databases (MongoDB [27], to name one
of many): both offer higher performance and scale better
than traditional databases, but provide weaker semantics.
Percolator extends Bigtable with multi-row, dis-
tributed transactions, and it provides the observer inter-
face to allow applications to be structured around notifi-
cations of changed data. We considered building the new
indexing system directly on Bigtable, but the complexity
of reasoning about concurrent state modification without
the aid of strong consistency was daunting. Percolator
does not inherit all of Bigtable’s features: it has limited
support for replication of tables across data centers, for
example. Since Bigtable’s cross data center replication
strategy is consistent only on a per-tablet basis, replica-
tion is likely to break invariants between writes in a dis-
tributed transaction. Unlike Dynamo and PNUTS which
serve responses to users, Percolator is willing to accept
the lower availability of a single data center in return for
stricter consistency.
Several research systems have, like Percolator, ex-
tended distributed storage systems to include strong con-
sistency. Sinfonia [3] provides a transactional interface
to a distributed repository. Earlier published versions of
Sinfonia [2] also offered a notification mechanism simi-
lar to the Percolator’s observer model. Sinfonia and Per-
colator differ in their intended use: Sinfonia is designed
to build distributed infrastructure while Percolator is in-
tended to be used directly by applications (this probably
explains why Sinfonia’s authors dropped its notification
mechanism). Additionally, Sinfonia’s mini-transactions
have limited semantics compared to the transactions pro-
vided by RDBMSs or Percolator: the user must specify
a list of items to compare, read, and write prior to issu-
ing the transaction. The mini-transactions are sufficient
to create a wide variety of infrastructure but could be
limiting for application builders.
CloudTPS [34], like Percolator, builds an ACID-
compliant datastore on top of a distributed storage sys-
tem (HBase [23] or Bigtable). Percolator and CloudTPS
systems differ in design, however: the transaction man-
agement layer of CloudTPS is handled by an interme-
diate layer of servers called local transaction managers
that cache mutations before they are persisted to the un-
derlying distributed storage system. By contrast, Perco-
lator uses clients, directly communicating with Bigtable,
to coordinate transaction management. The focus of the
systems is also different: CloudTPS is intended to be a
backend for a website and, as such, has a stronger focus
on latency and partition tolerance than Percolator.
ElasTraS [12], a transactional data store, is architec-
turally similar to Percolator; the Owning Transaction
Managers in ElasTraS are essentially tablet servers. Un-
like Percolator, ElasTraS offers limited transactional se-
mantics (Sinfonia-like mini-transactions) when dynami-
cally partitioning the dataset and has no support for struc-
turing computation.
5 Conclusion and Future Work
We have built and deployed Percolator and it has been
used to produce Google’s websearch index since April,
2010. The system achieved the goals we set for reducing
the latency of indexing a single document with an accept-
able increase in resource usage compared to the previous
indexing system.
The TPC-E results suggest a promising direction for
future investigation. We chose an architecture that scales
linearly over many orders of magnitude on commodity
machines, but we’ve seen that this costs a significant 30-
fold overhead compared to traditional database architec-
tures. We are very interested in exploring this tradeoff
and characterizing the nature of this overhead: how much
is fundamental to distributed storage systems, and how
much can be optimized away?
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) = 263
264
Acknowledgments
Percolator could not have been built without the assis-
tance of many individuals and teams. We are especially
grateful to the members of the indexing team, our pri-
mary users, and the developers of the many pieces of in-
frastructure who never failed to improve their services to
meet our increasingly large demands.
References
[1] TPC benchmark E standard specification version 1.9.0. Tech.
rep., Transaction Processing Performance Council, September
2009.
[2] AGUILERA, M. K., KARAMANOLIS, C., MERCHANT, A.,
SHAH, M., AND VEITCH, A. Building distributed applications
using Sinfonia. Tech. rep., Hewlett-Packard Labs, 2006.
[3] AGUILERA, M. K., MERCHANT, A., SHAH, M., VEITCH, A.,
AND KARAMANOLIS, C. Sinfonia: a new paradigm for building
scalable distributed systems. In SOSP ’07 (2007), ACM, pp. 159—
174.
[4] BARU, C., FECTEAU, G., GOYAL, A., HSIAO, H.-I., JHIN-
GRAN, A., PADMANABHAN, S., WILSON, W., AND I HSIAO,
A. G. H. DB2 parallel edition, 1995.
[5] BERENSON, H., BERNSTEIN, P., GRay, J., MELTON, J.,
O’NEIL, E., AND O’NEIL, P. A critique of ANSI SQL isola-
tion levels. In SIGMOD (New York, NY, USA, 1995), ACM,
pp. 1-10.
[6] BERNSTEIN, P. A., AND GOODMAN, N. Concurrency control
in distributed database systems. ACM Computer Surveys 13, 2
(1981), 185-221.
[7] BORAL, H., ALEXANDER, W., CLAY, L., COPELAND, G.,
DANFORTH, S., FRANKLIN, M., HART, B., SMITH, M., AND
VALDURIEZ, P. Prototyping Bubba, a highly parallel database
system. IEEE Transactions on Knowledge and Data Engineering
2, 1 (1990), 4-24.
[8] BURROWS, M. The Chubby lock service for loosely-coupled
distributed systems. In 7th OSDI (Nov. 2006).
[9] CHANG, F., DEAN, J., GHEMAWAT, S., HSIEH, W. C., WAL-
LACH, D. A., BURROWS, M., CHANDRA, T., FIKES, A., AND
GRUBER, R. E. Bigtable: A distributed storage system for struc-
tured data. In 7th OSDI (Nov. 2006), pp. 205-218.
[10] CoNnDIE, T., CONWAY, N., ALVARO, P., AND HELLERSTIEN,
J. M. MapReduce online. In 7th NSDI (2010).
[11] Cooper, B. F., RAMAKRISHNAN, R., SRIVASTAVA, U., SIL-
BERSTEIN, A., BOHANNON, P., JACOBSEN, H.-A., PUZ, N.,
WEAVER, D., AND YERNENI, R. PNUTS: Yahoo!’s hosted data
serving platform. In Proceedings of VLDB (2008).
[12] Das, S., AGRAWAL, D., AND ABBADI, A. E. ElasTraS: An
elastic transactional data store in the cloud. In USENIX HotCloud
(June 2009).
[13] DEAN, J., AND GHEMAWAT, S. MapReduce: Simplified data
processing on large clusters. In 6th OSDI (Dec. 2004), pp. 137—
150.
[14] DECANDIA, G., HASTORUN, D., JAMPANI, M., KAKULAPATI,
G., LAKSHMAN, A., PILCHIN, A., SIVASUBRAMANIAN, S.,
VOSSHALL, P., AND VOGELS, W. Dynamo: Amazon’s highly
available key-value store. In SOSP ’07 (2007), pp. 205-220.
[15] DEWITT, D., GHANDEHARIZADEH, S., SCHNEIDER, D.,
BRICKER, A., HSIAO, H.-I., AND RASMUSSEN, R. The gamma
database machine project. JEEE Transactions on Knowledge and
Data Engineering 2 (1990), 44-62.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
[16]
[17]
[18]
[19]
[20]
[21]
[22]
[23]
[24]
[25]
[26]
[27]
[28]
[29]
[30]
[31]
[32]
[33]
[34]
[35]
DEWITT, D., AND STONEBRAKER, M. MapReduce: A
major step backwards. http://databasecolumn.vertica.com/
database-innovation/mapreduce-a-major-step-backwards/.
DEWITT, D. J., AND GRAY, J. Parallel database systems: the
future of database processing or a passing fad? SIGMOD Rec.
19, 4 (1990), 104-112.
EKANAYAKE, J., LI, H., ZHANG, B., GUNARATHNE, T., BAE,
S.-H., Qlu, J., AND FOx, G. Twister: A runtime for iterative
MapReduce. In The First International Workshop on MapReduce
and its Applications (2010).
GERSHENSON, C., AND PINEDA, L. A. Why does public trans-
port not arrive on time? The pervasiveness of equal headway in-
stability. PLoS ONE 4, 10 (10 2009).
GHEMAWAT, S., GOBIOFF, H., AND LEUNG, S.-T. The Google
file system. vol. 37, pp. 29-43.
GupTA, A., AND MUMICK, I. S. Maintenance of materialized
views: Problems, techniques, and applications, 1995.
Hadoop. http://hadoop.apache.org/.
HBase. http://hbase.apache.org/.
ISARD, M., BUDIU, M., YU, Y., BIRRELL, A., AND FETTERLY,
D. Dryad: Distributed data-parallel programs from sequential
building blocks. In EuroSys ’07 (New York, NY, USA, 2007),
ACM, pp. 59-72.
IYER, S., AND CUTTS, M. Help test some next-generation
infrstructure. http://googlewebmastercentral.blogspot.com/2009/
08/help-test- some-next- generation.html, August 2009.
LOGOTHETIS, D., OLSTON, C., REED, B., WEBB, K. C., AND
Yocum, K. Stateful bulk processing for incremental analytics.
In SoCC ’10: Proceedings of the Ist ACM symposium on cloud
computing (2010), pp. 51-62.
MongoDB. http://mongodb.org/.
PAGE, L., BRIN, S., MOTWANI, R., AND WINOGRAD, T. The
PageRank citation ranking: Bringing order to the web. Tech. rep.,
Stanford Digital Library Technologies Project, 1998.
PATON, N. W., AND DiAz, O. Active database systems. ACM
Computing Surveys 31, 1 (1999), 63-103.
PAVLO, A., PAULSON, E., RASIN, A., ABADI, D. J., DEWITT,
D. J., MADDEN, S., AND STONEBRAKER, M. A comparison of
approaches to large-scale data analysis. In SIGMOD ’09 (June
2009), ACM.
Popa, L., BUDIU, M., YU, Y., AND ISARD, M. DryadInc:
Reusing work in large-scale computations. In USENIX workshop
on Hot Topics in Cloud Computing (2009).
STONEBRAKER, M. Operating system support for database man-
agement. Communications of the ACM 24, 7 (1981), 412-418.
NEC Express5800/A1080a-E TPC-E results. http://www.tpc.org/
tpce/results/tpce_result_detail.asp?id=1 10033001, Mar. 2010.
WEI, Z., PIERRE, G., AND CHI, C.-H. CloudTPS: Scalable
transactions for Web applications in the cloud. Tech. Rep. IR-
CS-053, Vrije Universiteit, Amsterdam, The Netherlands, Feb.
2010. http://www. globule.org/publi/CSTWAC_ircs53.html.
ZAHARIA, M., CHOWDHURY, M., FRANKLIN, M., SHENKER,
S., AND STOICA, I. Spark: Cluster computing with working sets.
In 2nd USENIX workshop on Hot Topics in Cloud Computing
(2010).
USENIX Association
USENIX Association
Reining in the Outliers in Map-Reduce Clusters using Mantri
Ganesh Ananthanarayanan!°
Ton Stoica® Yi Lu
Srikanth Kandula‘
Bikas Saha?
Albert Greenberg!
Edward Harris*
"Microsoft Research ° UC Berkeley * Microsoft Bing
Abstract- Experience from an operational Map-Reduce
cluster reveals that outliers significantly prolong job com-
pletion. The causes for outliers include run-time con-
tention for processor, memory and other resources, disk
failures, varying bandwidth and congestion along net-
work paths and, imbalance in task workload. We present
Mantri, a system that monitors tasks and culls outliers us-
ing cause- and resource-aware techniques. Mantri’s strate-
gies include restarting outliers, network-aware placement
of tasks and protecting outputs of valuable tasks. Using
real-time progress reports, Mantri detects and acts on out-
liers early in their lifetime. Early action frees up resources
that can be used by subsequent tasks and expedites the job
overall. Acting based on the causes and the resource and
opportunity cost of actions lets Mantri improve over prior
work that only duplicates the laggards. Deployment in
Bing’s production clusters and trace-driven simulations
show that Mantri improves job completion times by 32%.
1 Introduction
In a very short time, Map-Reduce has become the domi-
nant paradigm for large data processing on compute clus-
ters. Software frameworks based on Map-Reduce [1, 11,
13] have been deployed on tens of thousands of machines
to implement a variety of applications, such as building
search indices, optimizing advertisements, and mining
social networks.
While highly successful, Map-Reduce clusters come
with their own set of challenges. One such challenge is
the often unpredictable performance of the Map-Reduce
jobs. A job consists of a set of tasks which are organized in
phases. Tasks in a phase depend on the results computed
by the tasks in the previous phase and can run in paral-
lel. When a task takes longer to finish than other similar
tasks, tasks in the subsequent phase are delayed. At key
points in the job, a few such outlier tasks can prevent the
rest of the job from making progress. As the size of the
cluster and the size of the jobs grow, the impact of outliers
increases dramatically. Addressing the outlier problem is
critical to speed up job completion and improve cluster
efficiency.
Even a few percent of improvement in the efficiency
of a cluster consisting of tens of thousands of nodes can
save millions of dollars a year. In addition, finishing pro-
duction jobs quickly is a competitive advantage. Doing
so predictably allows SLAs to be met. In iterative mod-
ify/ debug/ analyze development cycles, the ability to it-
erate faster improves programmer productivity.
In this paper, we characterize the impact and causes
of outliers by measuring a large Map-Reduce production
cluster. This cluster is up to two orders of magnitude
larger than those in previous publications [1, 13, 20] and
exhibits a high level of concurrency due to many jobs si-
multaneously running on the cluster and many tasks on
a machine. We find that variation in completion times
among functionally similar tasks is large and that outliers
inflate the completion time of jobs by 34% at median.
We identify three categories of root causes for outliers
that are induced by the interplay between storage, net-
work and structure of Map-Reduce jobs. First, machine
characteristics play a key role in the performance of tasks.
These include static aspects such as hardware reliabil-
ity (e.g., disk failures) and dynamic aspects such as con-
tention for processor, memory and other resources. Sec-
ond, network characteristics impact the data transfer rates
of tasks. Datacenter networks are over-subscribed leading
to variance in congestion among different paths. Finally,
the specifics of Map-Reduce leads to imbalance in work -
partitioning data over a low entropy key space often leads
to a skew in the input sizes of tasks.
We present Mantri’, a system that monitors tasks and
culls outliers based on their causes. It uses the follow-
ing techniques: (i) Restarting outlier tasks cognizant of
resource constraints and work imbalances, (ii) Network-
aware placement of tasks, and (iii) Protecting output of
tasks based on a cost-benefit analysis.
The detailed analysis and decision process employed by
Mantri is a key departure from the state-of-the-art for out-
lier mitigation in Map-Reduce implementations [11, 13,
20]; these focus only on duplicating tasks. To our knowl-
edge, none of them protect against data loss induced re-
computations or network congestion induced outliers.
Mantri places tasks based on the locations of their data
sources as well as the current utilization of network links.
On a task’s completion, Mantri replicates its output if the
‘From Sanskrit, a minister who keeps the king’s court in order
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) = 265
266
benefit of not having to recompute outweighs the cost of
replication.
Further, Mantri performs intelligent restarting of out-
liers. A task that runs for long because it has more work
to do will not be restarted; if it lags due to reading data
over a low-bandwidth path, it will be restarted only if a
more advantageous network location becomes available.
Unlike current approaches that duplicate tasks only at the
end of a phase, Mantri uses real-time progress reports to
act early. While early action on outliers frees up resources
that could be used for pending tasks, doing so is nontriv-
ial. A duplicate may finish faster than the original task
but has the opportunity cost of consuming resources that
other pending work could have used.
In summary we make the following contributions.
First, we provide an analysis of the causes of outliers in
a large production Map-Reduce cluster. Second, we de-
velop Mantri, that takes early actions based on under-
standing the causes and the opportunity cost of actions.
Finally, we perform an extensive evaluation of Mantri and
compare it to existing solutions.
Mantri runs live in all of Bing’s production clusters since
May 2010. Results from a deployment of Mantri on a pro-
duction cluster of thousands of servers and from replay-
ing several thousand jobs collected on this cluster in a
simulator show that:
¢ Mantri reduces the completion time of jobs by 32% on
average on the production clusters. Extensive simula-
tions show that job phases are quicker by 21% and 42%
at the 5oth and 75th percentiles. Mantri’s median re-
duction in completion time improves on the next best
scheme by 3.1x while using fewer resources.
e By placing reduce tasks to avoid network hotspots,
Mantri improves the completion times of the reduce
phases by 60%.
« By preferentially replicating the output of tasks that are
more likely to be lost or expensive to recompute, Mantri
speeds up half of the jobs by at least 20% each while
only increasing the network traffic by 1%.
2 Background
We monitored the cluster and software systems that sup-
port the Bing search engine for over twelve months. This
is a cluster of tens of thousands of commodity servers
managed by Cosmos [8], a proprietary upgraded form of
Dryad [13]. Despite a few differences, implementations
of Map-Reduce [1, 8, 11, 13] are broadly similar.
Most of the jobs in the examined cluster are written
in Scope [8], a mash-up language that mixes SQL-like
declarative statements with user code. The Scope com-
piler transforms a job into a workflow- a directed acyclic
graph where each node is a phase and each edge joins a
phase that produces data to another that uses it. A phase
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
is a set of one or more tasks that run in parallel and per-
form the same computation on different parts of the in-
put stream. Typical phases are map, reduce and join. The
number of tasks in a phase is chosen at compile time. A
task will read its input over the network if it is not avail-
able on the local disk but outputs are written to the local
disk. The eventual outputs of a job (as well as raw data) are
stored in a reliable block storage system implemented on
the same servers that do computation. Blocks are repli-
cated n-ways for reliability. A run-time scheduler assigns
tasks to machines, based on data locations, dependence
patterns and cluster-wide resource availability. The net-
work layout provides more bandwidth within a rack than
across racks.
We obtain detailed logs from the Scope compiler and
the Cosmos scheduler. At each of the job, phase and task
levels, we record the execution behavior as represented
by begin and end times, the machines(s) involved, the
sizes of input and output data, the fraction of data that
was read across racks and a code denoting the success or
type of failure. We also record the workflow of jobs. Ta-
ble 1 depicts the random subset of logs that we analyze
here. Spanning eighteen days, this dataset is at least one
order of magnitude larger than prior published data along
many dimensions, e.g., number of jobs, cluster size.
3 The Outlier Problem
We begin with a first principles approach to the outlier
problem, then analyze data from the production cluster
to quantify the problem and obtain a breakdown of the
causes of outliers ($4). Beginning at the first principles
motivates a distinct approach (§5) which as we show in $6
significantly improves on prior art.
3.1 Outliers in a Phase
Assume a phase consists of n tasks and has s slots. Slot is a
virtual token, akin to a quota, for sharing cluster resources
among multiple jobs. One task can run per slot at a time.
On our cluster, the median ratio of 4 is 2.11 with a stdev
of 12.37. The goal is to minimize the phase completion
time, i.e., the time when the last task finishes.
Based on data from the production cluster, we model
t;, the completion time of task 7, as a function of the size
of the data it processes, the code it runs, the resources
available on the machine it executes and the bandwidth
available on the network paths involved:
t; = f (datasize, code, machine, network). — (1)
Large variation exists along each of the four variables
leading to considerable difference in task completion
times. The amount of data processed by tasks in the same
phase varies, sometimes widely, due to limitations in di-
viding work evenly. The code is the same for tasks in a
USENIX Association
USENIX Association
Dates Network
2009-710
Phases Jobs
x 103
Compute | Data
(years) (PB) (PB)
May 25,26 19.0 938 49.1 12.6 -66
Jun 16,17 16.5 991 88.0 22.7 132
Jul 20,21 22.0 1183 51.6 14.3 .67
Aug 20,21 29.2 1873 60.6 18.7 -76
Sep 15,16 27.4 1653 73.0 22.8 73
Oct 15,16 20.4 1362 84.1 25.3 .86
Nov 16,17 37.8 1834 88.4 25.0 -68
Dec 10,11 18.7 1777 96.2 18.6 57.2
Jan 11,12 24.4 1842 79.5 21.5 1.99
Table 1: Details of the logs from a production cluster consisting
of thousands of servers.
phase, but differs significantly across phases (e.g., map
and reduce). Placing a task on a machine that has other
resource hungry tasks inflates completion time, as does
reading data across congested links.
In the ideal scenario, where every task takes the same
amount of time, say 7’, scheduling is simple. Any
work-conserving schedule would complete the phase in
( [2] x T) . When the task completion time varies, how-
ever, a naive work-conserving scheduler can take up to
— + max ti). A large variation in t; increases the
term max t; and manifests as outliers.
The goal of a scheduler is to minimize the phase com-
pletion time and make it closer to 2m s Sometimes, it
can do even better. By placing tasks at less congested ma-
chines or network locations, the ¢;’s themselves can be
lowered. The challenge lies in recognizing the aspects that
can be changed and scheduling accordingly.
3.2 Extending from a phase to a job
The phase structure of Map-Reduce jobs adds to the vari-
ability. An outlier in an early phase, by delaying when
tasks that use its output may start, has cumulative effects
on the job. At barriers in the workflow, where none of
the tasks in successive phase(s) can begin until all of the
tasks in the preceding phase(s) finish, even one outlier
can bring the job to a standstill’. Barriers occur primar-
ily due to reduce operations that are neither commuta-
tive nor associative [28], for instance, a reduce that com-
putes the median of records that have the same key. In
our cluster, the median job workflow has eight phases and
eleven edges, 47% are barriers (number of edges exceeds
the number of phases due to table joins).
Dependency across phases also leads to outliers when
task output is lost and needs to be recomputed. Data loss
happens due to a combination of disk errors, software er-
*There is a variant in implementation where a slot is reserved for
a task before all its inputs are ready. This is either to amortize the
latency of network transfer by moving data over the network as soon
as it is generated [1, 11], or compute partial results and present answers
online even before the job is complete [9]. Regardless, pre-allocation
of slots hogs resources for longer periods if the input task(s) straggle.
ple p> E> En
System Extract Partition Neste LAS)
Barrier
(a) Partial workflow with the number of tasks in each phase
3 : Extract
oe ae Partition - - - -
Se ; Aggregate v---
oe ih.
= .
2 > 05
ES 04
ze 0.3
SS = 0.2
= 0.1 i :
3 0 - i Be is
=a 0 0.1 0.2 0.3 0.4 0.5
Time (Normalized by Job Lifetime)
(b) Time lapse of task execution (R=Recomputes, B=Barrier).
Figure 1: An example job from the production cluster
rors (e.g., bugs in garbage collectors) and timeouts due to
machines going unresponsive at times of high load. In
fact, recomputes cause some of the longest waiting times
observed on the production cluster. A recompute can cas-
cade into earlier phases if the inputs for the recomputed
task are no longer available and need to be regenerated.
3.3 Illustration of Outliers
Figure 1(a) shows the workflow for a job whose structure
is typical of those in the cluster. The job reads a dataset
of search usage and derives an index. It consists of two
Map-Reduce operations and a join, but for clarity we only
show the first Map-Reduce here. Phase names follow the
Dryad [13] convention- extract reads raw blocks, parti-
tion divides data on the key and aggregate reduces items
that share a key.
Figure 1(b) depicts a timeline of an execution of this
workflow. It plots the number of tasks of each phase that
are active, normalized by the maximum tasks active at any
time in that phase, over the lifetime of the job. Tasks in
the first two phases start in quick succession to each other
at ~.05, whereas the third starts after a barrier.
Some of the outliers are evident in the long lulls before
a phase ends when only a few of its tasks are active. In par-
ticular, note the regions before x~.1 and x~.5. The spike
in phase #2 here is due to the outliers in phase #1 holding
on to the job's slots. At the barrier, x~.1, just a few outliers
hold back the job from making forward progress. Though
most aggregate tasks finish at x~.3, the phase persists for
another 20%.
The worst cases of waiting immediately follow recom-
putations of lost intermediate data marked by R. Recom-
putations manifest as tiny blips near the x axes for phases
that had finished earlier, e.g., phase #2 sees recomputes at
x~.2 though it finished at x~.1. At x~.2, note that aggre-
gate almost stops due to a few recomputations.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 710) 267
268
We now quantify the magnitude of the outlier problem,
before presenting our solution in detail.
4 Quantifying the Outlier Problem
We characterize the prevalence and causes of outliers and
their impact on job completion times and cluster resource
usage. We will argue that three factors - dynamics, con-
currency and scale, that are somewhat unique to large
Map-Reduce clusters for efficient and economic opera-
tion, lie at the core of the outlier problem. To our knowl-
edge, we are the first to report detailed experiences from
a large production Map-Reduce cluster.
4.1 Prevalence of Outliers
Figure 2(a) plots the fraction of high runtime outliers and
recomputes in a phase. For exposition, we arbitrarily say
that a task has high runtime if its time to finish is longer
than 1.5x the median task duration in its phase. By re-
computes, we mean instances where a task output is lost
and dependent tasks wait until the output is regenerated.
We see in Figure 2(a) that 25% of phases have more
than 15% of their tasks as outliers. The figure also shows
that 99% of the phases see no recomputes. Though rare,
recomputes have a widespread impact ($4.3). Two out of
a thousand phases have over 50% of their tasks waiting for
data to be recomputed.
How much longer do outliers run for? Figure 2(b)
shows that 80% of the runtime outliers last less than 2.5
times the phase’s median task duration, with a uniform
probability of being delayed by between 1.5x to 2.5x. The
tail is heavy and long- 10% take more than 10x the me-
dian duration. Ignoring these if they happen early in a
phase, as current approaches do, appears wasteful.
Figure 2(b) shows that most recomputations behave
normally, 90% of them are clustered about the median
task, but 3% take over 10x longer.
4.2 Causes of Outliers
To tease apart the contributions of each cause, we first de-
termine whether a task’s runtime can be explained by the
amount of data it processes or reads across the network’.
If yes, then the outlier is likely due to workload imbalance
or poor placement. Otherwise, the outlier is likely due to
resource contention or problematic machines.
Figure 3(a) shows that in 40% of the phases (top right),
all the tasks with high runtimes (i-e., over 1.5x the me-
*For each phase, we fit a linear regression model for task lifetime
given the size of input and the volume of traffic moved across low
bandwidth links. When the residual error for a task is less than 20%,
ie., its run time is within [.8, 1.2]x of the time predicted by this model,
we call it explainable.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
0.8
0.6
high runtime ot 5
recompute ----- 0.2 5
high runtime
recompute -----
esceosescessso
HANWAUOMANWDO=—
Cumulative
0
012 4 6 8 10
Ratio of Straggler Duration to the
Duration of the Median Task
0 01 02 03 04 05
Fraction of Outliers
Cumulative Fraction of Phases
(a) What fraction of tasks in a
phase are outliers?
(b) How much longer do out-
liers take to finish?
Figure 2: Prevalence of Outliers.
dian task) are well explained by the amount of data they
process or move on the network. Duplicating these tasks
would not make them run faster and will waste resources.
At the other extreme, in 18% of the phases (bottom left),
none of the high runtime tasks are explained by the data
they process. Figure 3(b) shows tasks that take longer
than they should, as predicted by the model, but do not
take over 1.5x the median task in their phase. Such tasks
present an opportunity for improvement. They may fin-
ish faster ifrun elsewhere, yet current schemes do nothing
for them. 20% of the phases (on the top right) have over
55% of such improvable tasks.
Data Skew: It is natural to ask why data size varies across
tasks in a phase. Across phases, the coefficient of vari-
ation (sider) in data size is .34 and 3.1 at the 50°” and
go'” percentiles. From experience, dividing work evenly
is non-trivial for a few reasons. First, scheduling each ad-
ditional task has overhead at the job manager. Network
bandwidth is another reason. There might be too much
data on a machine for a task to process, but it may be
worse to split the work into multiple tasks and move data
over the network. A third reason is poor coding practice.
If the data is partitioned on a key space that has too little
entropy, i.e., a few keys correspond to a lot of data, then
the partitions will differ in size. Some reduce tasks are not
amenable to splitting (neither commutative nor associa-
tive [27]), and hence each partition has to be processed by
one task. Some joins and sorts are similarly constrained.
Duplicating tasks that run for long because they have a lot
of work to do is counter-productive.
Crossrack Traffic: Reduce phases contribute over 70%
of the cross rack traffic in the cluster, while most of the
rest is due to joins. We focus on cross rack traffic because
the links upstream of the racks have less bandwidth than
the cumulative capacity of servers in the rack.
We find that crossrack traffic leads to outliers in two
ways. First, in phases where moving data across racks is
avoidable (through locality constraints), a task that ends
up in a disadvantageous network location runs slower
than others. Second, in phases where moving data across
racks is unavoidable, not accounting for the competition
among tasks within the phase (self-interference) leads to
outliers. In a reduce phase, for example, each task reads
USENIX Association
USENIX Association
Cumulative #Phases
Cumulative #Phases
0 20 40 60 80 100 0 20 40 60 80 100
% of tasks that have high % of tasks that are unexplainably
runtime but are explainable long, but not long per-se
(a) 1)
Figure 3: Contribution of data size to task runtime (see $4.2)
« 100
80
60
40
20 i
0
'(62.8%)
CDF % Phase Tim
0 20 40 60 80 100
Ideal Redn. (%) in Completion Time
Figure 4: For reduce phases, the reduction in comple-
tion time over the current placement by placing tasks in a
network-aware fashion.
po1.4 i090
<x 80
£13 --CPU Ratio 2
a] --Memory Ratio 60 &
> 1.2 3
2 40€&
E 1.1 S
91. an
cw
1 0
0 10 20 30
Fraction of Cluster (%)
Figure 5: The ratio of processor and memory usage when
recomputations happen to the average at that machine (y1).
Also, the cumulative percentage of recomputations across ma-
chines (y2).
from every map task. Since the maps are spread across the
cluster, regardless of where a reduce task is placed, it will
read a lot of data from other racks. Current implementa-
tions place reduce tasks on any machine with spare slots.
A rack that has too many reduce tasks will be congested
on its downlink leading to outliers.
Figure 4 compares the current placement with an ideal
one that minimizes the impact of network transfer. When
possible it avoids reading data across racks and if not,
places tasks such that their competition for bandwidth
does not result in hotspots. In over 50% of the jobs, reduce
phases account for 17% of the job’s lifetime. For the re-
duce phases, the figure shows that the median phase takes
62% longer under the current placement.
Bad and Busy Machines: We rarely find machines that
persistently inflate runtimes. Recomputations, however,
are more localized. Half of them happen on 5% of the ma-
chines in the cluster. Figure 5 plots the cumulative share
of recomputes across machines on the axes on the right.
The figure also plots the ratio of processor and memory
utilization during recomputes to the overall average on
that machine. The occurrence of recomputes is correlated
300 /Ugde! outliers...
250
200
150
100
50
recomputes ©
Time~(minutes)
0 200 400 600 800 100C
Machine Id
Figure 6: Clustering recomputations and outliers.
with increased use of resources by at least 20%. The sub-
set of machines that triggers most of the recomputes is
steady over days but varies over weeks, likely indicative
of changing hotspots in data popularity or corruption in
disks [7].
Figure 6 investigates the occurrence of “spikes” in out-
liers. For legibility, we only plot a subset of the machines.
We find that runtime outliers (shown as stars) cluster by
time. If outliers were happening at random, there should
not be any horizontal bands. Rather it appears that jobs
contend for resources at some times. Even at these busy
times, other lightly loaded machines exist. Recomputa-
tions (shown as circles) cluster by machine. When a ma-
chine loses the output of a task, it has a higher chance of
losing the output of other tasks.
Rarely does an entire rack of servers experience the
same anomaly. When an anomaly happens, the frac-
tion of other machines within the rack that see the same
anomaly is less than ~ for recomputes, and _ for run-
time with high probability. So, it is possible to restart a
task, or replicate output to protect against loss on another
machine within the same rack as the original machine.
4.3 Impact of Outliers
We now examine the impact of outliers on job comple-
tion times and cluster usage. Figure 7 plots the CDF for
the ratio of job completion times, with different types of
outliers included, to an ideal execution that neither has
skewed run times nor loses intermediate data. The y-axes
weighs each job by the total cluster time its tasks take to
run. The hypothetical scenarios, with some combination
of outliers present but not the others, do not exist in prac-
tice. So we replayed the logs in a trace driven simulator
that retains the structure of the job, the observed task du-
rations and the probabilities of the various anomalies (de-
tails in §6). The figure shows that at median, the job com-
pletion time would be lower by 15% if runtime outliers
did not happen, and by more than 34% when none of the
outliers happen. Recomputations impact fewer jobs than
runtime outliers, but when they do, they delay comple-
tion time by a larger amount.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) 269
270
ny
So
o
oo
o
a
o
- <7 +-No Stragglers
{
a —No Recomputes
! --Neither
i(34.7%)
ry
o
CDF % Job Time
N
o
0 20 40 60 80 4100
Ideal Redn. (%) in Completion Time
Figure 7: Percentage speed-up of job completion time in the
ideal case when (some combination of) outliers do not occur.
ofa) neem ial elea
Task Operations
Contention
emcee CS
Outlier
Causes
* duplicate
* kill, restart
Solutions
placement * pre-compute do more first
Figure 8: The Outlier Problem: Causes and Solutions
By inducing high variability in repeat runs of the same
job, outliers make it hard to meet SLAs. At median, the
ratio of Sa in job completion time is 0.8, i-e., jobs have a
non-trivial probability of taking twice as long or finishing
half as quickly.
To summarize, we take the following lessons from our
experience.
¢ High running times of tasks do not necessarily indicate
slow execution - there are multiple reasons for legiti-
mate variation in durations of tasks.
e Every job is guaranteed some slots, as determined by
cluster policy, but can use idle slots of other jobs.
Hence, judicious usage of resources while mitigating
outliers has collateral benefit.
¢ Recomputations affect jobs disproportionately. They
manifest in select faulty machines and during times of
heavy resource usage. Nonetheless, there are no indi-
cations of faulty racks.
5 Mantri Design
Mantri identifies points at which tasks are unable to make
progress at the normal rate and implements targeted solu-
tions. The guiding principles that distinguish Mantri from
prior outlier mitigation schemes are cause awareness and
resource cognizance.
Distinct actions are required for different causes. Fig-
ure 8 specifies the actions Mantri takes for each cause. Ifa
task straggles due to contention for resources on the ma-
chine, restarting or duplicating it elsewhere can speed it
up ($5.1). However, not moving data over the low band-
width cross rack links, and if unavoidable, doing so while
avoiding hotspots requires systematic placement (§5.2).
To speed up tasks that wait for lost input to be recom-
puted, we find ways to protect task output (§5.3). Finally,
for tasks with a work imbalance, we schedule the large
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
slots
1
2
1
2 WHEE
time
1
2 t
baseline,
w/o early time
duplicate
kill, restart
time
Figure 9: A stylized example to illustrate our main ideas. Tasks
that are eventually killed are filled with stripes, repeat instances
of a task are filled with a lighter mesh.
tasks before the others to avoid being stuck with the large
ones near completion (§5.4).
There is a subtle point with outlier mitigation: reduc-
ing the completion time of a task may in fact increase the
job completion time. For example, replicating the output
of every task will drastically reduce recomputations—both
copies are unlikely to be lost at the same time, but can
slow down the job because more time and bandwidth are
used up for this task denying resources to other tasks that
are waiting to run. Similarly, addressing outliers early in
a phase vacates slots for outstanding tasks and can speed
up completion. But, potentially uses more resources per
task. Unlike Mantri, none of the existing approaches act
early or replicate output. Further, naively extending cur-
rent schemes to act early without being cognizant of the
cost of resources, as we show in $6, leads to worse perfor-
mance.
Closed-loop action allows Mantri to act optimistically
by bounding the cost when probabilistic predictions go
awry. For example, even when Mantri cannot ascertain
the cause of an outlier, it experimentally starts copies. If
the cause does not repeatedly impact the task, the copy
can finish faster. To handle the contrary case, Mantri con-
tinuously monitors running copies and kills those whose
cost exceeds the benefit.
Based on task progress reports, Mantri estimates for
each task the remaining time to finish, t,e;,, and the pre-
dicted completion time of a new copy of the task, trew.
Tasks report progress once every 10s or ten times in their
lifetime, whichever is smaller. We use A to refer to this
period. We defer details of the estimation to §5.5 and pro-
ceed to describe the algorithms for mitigating each of the
main causes of outliers. All that matters is that t,e., be
an accurate estimate and that the predicted distribution
tnew account for the underlying work that the task has to
do, the appropriateness of the network location and any
persistent slowness of the new machine.
5.1 Resource-aware Restart
We begin with a simple example to help exposition. Fig-
ure 9 shows a phase that has seven tasks and two slots.
USENIX Association
USENIX Association
1: let A = period of progress reports
2: let c = number of copies of a task
3: periodically, for each running task, kill all but the fastest a copies
after A time has passed since begin
4: while slots are available do
5 if tasks are waiting for slots then
6 kill, restart task iftrem > E(tnew) +A, stop at ¥ restarts
a: duplicate if P(trem > tnew ott) >6
8: start the waiting task that has the largest data to read
9 else > all tasks have begun
10: duplicate iff E(tnew — trem) > pA
141: end if
12: end while
Pseudocode 1: Algorithm for Resource-aware restarts (simpli-
fied).
Normal tasks run for times ¢t and 2¢. One outlier has a
runtime of 5¢. Time increases along the x axes.
The timeline at the top shows a baseline which ignores
outliers and finishes at 7t. Prior approaches that only ad-
dress outliers at the end of the phase also finish at 7¢.
Note that if this outlier has a large amount of data to
process letting the straggling task be is better than killing
or duplicating it, both of which waste resources.
If however, the outlier was slowed down by its loca-
tion, the second and third timelines compare duplication
to a restart that kills the original copy. After a short time
to identify the outlier, the scheduler can duplicate it at
the next available slot (the middle time-line) or restart it
in-place (the bottom timeline). If prediction is accurate,
restarting is strictly better. However, if slots are going idle,
it may be worthwhile to duplicate rather than incur the
risk of losing work by killing.
Duplicating the outlier costs a total of 3¢ in re-
sources (2t before the original task is killed and ¢ for the
duplicate) which may be wasteful if the outlier were to
finish in sooner than 3t by itself.
Restart Algorithm: Mantri uses two variants of restart,
the first kills a running task and restarts it elsewhere,
the second schedules a duplicate copy. In either method,
Mantri restarts only when the probability of success, i.e.,
P(tnew < trem) is high. Since trey accounts for the sys-
tematic differences and the expected dynamic variation,
Mantri does not restart tasks that are normal (e.g., runtime
proportional to work). Pseudocode 1 summarizes the al-
gorithm. Mantri kills and restarts a task if its remaining
time is so large that there is a more than even chance that
a restart would finish sooner. In particular, Mantri does so
when trem > E(tnew) +A *. To not thrash on inaccurate
estimates, Mantri kills a task no more than y = 3 times.
The “kill and restart" scheme drastically improves the
job completion time without requiring extra slots as we
show analytically in [5]. However, the current job sched-
uler incurs a queueing delay before restarting a task, that
“Since the median of the heavy tailed task completion time
distribution is smaller than the mean, this check implies that
P (tnrew < trem) > P (tnew < E(tnew)) 25
(a) Ad-hoc placement
(b) Even spread
Figure 10: Three reduce tasks (rhombus boxes) are to be placed
across three racks. The rectangles indicate their input. The type
of the rectangle indicates the map that produced this data. Each
reduce task has to process one shard of each type. The ad-hoc
placement on the left creates network bottlenecks on the cross-
rack links (highlighted). Tasks in such racks will straggle. If the
network has no other traffic, the even placement on the right
avoids hotspots.
can be large and highly variant. Hence, we consider
scheduling duplicates.
Scheduling a duplicate results in the minimum com-
pletion time of the two copies and provides a safety net
when estimates are noisy or the queueing delay is large.
However, it requires an extra slot and if allowed to run
to finish, consumes extra computation resource that will
increase the job completion time if outstanding tasks are
prevented from starting. Hence, when there are outstand-
ing tasks and no spare slots, we schedule a duplicate only
if the total amount of computation resource consumed
decreases. In particular, if c copies of the task are cur-
rently running, a duplicate is scheduled only if P(trem >
tne ott) > 6. By default, 6 = .25. For example, a
task with one running copy is duplicated only if tnew is
less than half of tem. For stability, Mantri does not re-
duplicate a task for which it launched a copy recently. Any
copy that has run for some time and is slower than the
second fastest copy of the task will be killed to conserve
resources. Hence, there are never more than three run-
ning copies of a task’. When spare slots are available, as
happens towards the end of the job, Mantri schedules du-
plicates more aggressively, i.e., whenever the reduction in
the job completion time is larger than the start up time,
U(tnew —trem) > pA. By default, p = 3. Note that in all
the above cases, if more than one task satisfies the neces-
sary conditions, Mantri breaks ties in favor of the task that
will benefit the most.
Mantri’s restart algorithm is independent of the values
for its parameters. Setting y to a larger and p,6 to a
smaller value trades off the risk of wasteful restarts for
the reward of a larger speed-up. The default values that
are specified here err on the side of caution.
By scheduling duplicates conservatively and pruning
aggressively, Mantri has a high success rate of its restarts.
As a result, it reduces completion time and conserves re-
sources ( $6.2).
°The two fastest copies and the copy that has recently started.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI’10) = 271
272
5.2 Network-Aware Placement
Reduce tasks, as noted before ($4.2), have to read data
across racks. A rack with too many reduce tasks is con-
gested on its downlink and such tasks will straggle. Fig-
ure 10 illustrates such a scenario.
Given the utilization of all the network links and the
locations of inputs for all the tasks (and jobs) that are
waiting to run, optimally placing the tasks to minimize
job completion time is a form of the centralized traffic
engineering problem [14, 18]. However achieving up-
to-date information of network state and centralized co-
ordination across all jobs in the cluster are challenging.
Instead, Mantri approximates the optimal placement by
a local algorithm that does not track bandwidth changes
nor require co-ordination across jobs.
With Mantri, each job manager places tasks so as
to minimize the load on the network and avoid self-
interference among its tasks. If every job manager takes
this independent action, network hotspots will not cause
outliers. Note that the sizes of the map outputs in each
rack are known to the job manager prior to placing the
tasks of the subsequent reduce phase. For a reduce phase
with n tasks running on a cluster with r racks, let its input
matrix J,, , specify the size of input in each rack for each
of the tasks®. For any placement of reduce tasks to racks,
let the data to be moved out (on the uplink) and read in
(on the downlink) on the i” rack be di, d’,, and the corre-
sponding available bandwidths be b’, and b’, respectively.
d
For each rack, we compute two terms co;-1 = a and
C44 = oe. The first term is the ratio of outgoing traffic
d
and available uplink bandwidth, and the second term is
the ratio of incoming traffic and available downlink band-
width. The algorithm computes the optimal value over all
placement permutations, i.e., the rack location for each
task that minimizes the maximum data transfer time, as
argminmax;¢;, Jj =1,--- ,2n,.
Rather than track the available bandwidths b’, and b’,
as they change with time and as a function of other jobs
in the cluster, Mantri uses these estimates. Reduce phases
with a small amount of data finish quickly, and the band-
widths can be assumed to be constant throughout the ex-
ecution of the phase. For phases with a large amount of
data, the bandwidth averaged over their long lifetime can
be assumed to be equal for all links. We see that with these
estimates Mantri’s placement comes close to the ideal in
our experiments (see §6.4).
For phases other than reduce, Mantri complements the
Cosmos policy of placing a task close to its data [23]. By
accounting for the cost of moving data over low band-
width links in tw, Mantri ensures that no copy is started
°In I, the row sum indicates the data to be read by the task, whereas
the column sum indicates the total input present in that rack.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
A wnrenlicy
replica
M27) M rere)
cen = r(ty sr)
Ome
U redo “oto
ry =r i replica. i orig
(a) (b) (c)
replication rai
t cost Pp
yes
Pe ee In budget?) aleres
cost
ax(t! riedas a2 pours)
(d) Decision Process
Figure 11: Avoiding costly recomputations: The cost to redo a
task includes the recursive probability of predecessor tasks hav-
ing to be re-done (a). Replicating output reduces the effective
probability of loss (b). Tasks with many-to-one input patterns
have high recomputation cost and are more valuable (c).
at a location where it has little chance of finishing earlier
thereby not wasting resources.
5.3 Avoiding Recomputation
To mitigate costly recomputations that stall a job, Mantri
protects against interim data loss by replicating task out-
put. It acts early by replicating those outputs whose cost to
recompute exceeds the cost to replicate. Mantri estimates
the cost to recompute as the product of the probability
that the output will be lost and the time to repeat the task.
The probability of loss is estimated for a machine over a
long period of time. The time to repeat the task is tredo
with a recursive adjustment that accounts for the task’s
inputs also being lost. Figure 11 illustrates the calcula-
tion of t,-edo based on the data loss probabilities (r;’s), the
time taken by the tasks (¢;’s) and recursively looks at prior
phases. Replicating the output reduces the likelihood of
recomputation to the case when all replicas are unavail-
able. Ifa task reads input from many tasks (e.g., a reduce),
tredo is higher since any of the inputs needing to be re-
computed will stall the task’s recomputation ’. The cost
to replicate, tcp, is the time to move the data to another
machine in the rack.
In effect, the algorithm replicates tasks at key places in
a job’s workflow — when the cumulative cost of not repli-
cating many successive tasks builds up or when tasks ran
on very flaky machines (high r;) or when the output is so
small that replicating it would cost little (low trep).
Further, to avoid excessive replication, Mantri limits the
amount of data replicated to 10% of the data processed by
the job. This limit is implemented by granting tokens pro-
portional to the amount of data processed by each task.
Task output that satisfies the above cost-benefit check is
7In Fig. 11(c), we assume that if multiple inputs are lost, they are re-
computed in parallel and the task is stalled by the longest input. Since
recomputes are rare (Fig. 2(a)), this is a fair approximation of practice.
USENIX Association
USENIX Association
replicated only if an equal number of tokens are available.
Tokens are deducted on replication.
Mantri proactively recomputes tasks whose output and
replicas, if any, have been lost. From §4, we see that re-
computations on a machine cluster by time, hence Mantri
considers a recompute to be the onset of a temporal prob-
lem which will cause future requests for data on this ma-
chine to fail and pre-computes such output. Doing so de-
creases the time that a dependent task will have to wait
for lost input to be regenerated. As before, Mantri im-
poses a budget on the extra cluster cycles used for pre-
computation. Together, probabilistic replication and pre-
computation approximate the ideal scheme in our evalu-
ation ($6.5).
5.4 Data-aware Task Ordering
Workload imbalance causes tasks to straggle. Mantri does
not restart outliers that take a long time to run because
they have more work to do. Instead, Mantri improves job
completion time by scheduling tasks in a phase in de-
scending order of their input size. Given n tasks, s slots
and input sizes d[1---nJ], if the optimal completion time
is Tc, scheduling tasks in inverse order of their input sizes
will take T’, where ts < * _ a [12]. This means that
scheduling tasks with the longest processing time first is
at most 33% worse than the optimal schedule; computing
the optimal is NP-hard [12].
5.5 Estimation of t,.,, and tye.
Periodically, every running task informs the job sched-
uler of its status, including how many bytes it has read,
dread> thus far. Mantri combines the progress reports with
the size of the input data that each task has to process,
d, and predicts how much longer the task would take to
finish using this model:
+r twrapup: (2)
read
The first term captures the remaining time to process
data. The second term is the time to compute after all
the input has been read and is estimated from the be-
havior of earlier tasks in the phase. Tasks may speed up
or slow down and hence, rather than extrapolating from
each progress report, Mantri uses a moving average. To
be robust against lost progress reports, when a task hasn't
reported for a while, Mantri increases tem by assuming
that the task has not progressed since its last report. This
linear model for estimating the remaining time for a task
is well suited for data-intensive computations like Map-
Reduce where a task spends most of its time reading the
input data. We seldom see variance in computation time
among tasks that read equal amounts of data [26].
trem = telapsed
Mantri estimates t,,¢,,, the distribution over time that a
new copy of the task will take to run, as follows:
tnew = processRate * locationFactor * d + schedLag. (3)
The first term is a distribution of the process rate, i.e.,
se of all the tasks in this phase. The second term is
a relative factor that accounts for whether the candidate
machine for running this task is persistently slower (or
faster) than other machines or has smaller (or larger) ca-
pacity on the network path to where the task’s inputs are
located. The third term, as before, is the amount of data
the task has to process. The last term is the average delay
between a task being scheduled and when it gets to run.
We show in §6.2 that these estimates of tye, and tnew are
sufficiently accurate for Mantri’s functioning.
6 Evaluation
We deployed and evaluated Mantri on Bing’s production
cluster consisting of thousands of servers. Mantri has been
running as the outlier mitigation module for all the jobs
in Bing’s clusters since May 2010. To compare against a
wider set of alternate techniques, we built a trace driven
simulator that replays logs from production.
6.1 Setup
Clusters: The production cluster consists of thousands
of server-class multi-core machines with tens of GBs of
RAM that are spread roughly 40 servers to a rack. This
cluster is used by Bing product groups. The data we an-
alyzed earlier is from this cluster, so the observations
from $4 hold here.
Workload: Mantri is the default outlier mitigation solu-
tion for the production cluster. The jobs submitted to
this cluster are independent of us, enabling us to evalu-
ate Mantri’s performance in a live cluster across a variety of
production jobs. We compare Mantri’s performance on all
jobs in the month of June 2010 with prior runs of the same
jobs in April-May 2010 that ran with the earlier build of
Cosmos.
In addition, we also evaluate Mantri on four hand-
picked applications that represent common building
blocks. Word Count calculates the number of unique
words in the input. Table Join inner joins two tables each
with three columns of data on one of the columns. Group
By counts the number of occurrences of each word in the
file. Finally, grep searches for string patterns in the input.
We vary input sizes from 53 GB to 500 GB.
Prototype: Manitri builds on the Cosmos job scheduler
and consists of about 1000 lines of C++ code. To com-
pute ¢,.¢7, Mantri maintains an execution record for each
of the running tasks that is updated when the task reports
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) 273
274
progress. A phase-wide data structure stores the neces-
sary statistics to compute te,. When slots become avail-
able, Mantri runs Pseudocode 1 and restarts or duplicates
the task that would benefit the most or starts new tasks
in descending order of data size. To place tasks appropri-
ately, name builds on the per-task affinity list, a preferred
set of machines and racks that the task can run on. At
run-time the job manager attempts to place the task at
its preferred locations in random order, and when none
of them are available runs the task at the first available
slot. The affinity list for map tasks has machines that have
replicas of the input blocks. For reduce tasks, to obtain
the desired proportional spread across racks (see §5.2),
we populate the affinity list with a proportional number
of machines in those racks.
Trace-driven Simulator: The simulator replays the logs
shown in Table 1. For each phase, it faithfully repeats
the observed distributions of task completion times, data
read by each task, size and location of inputs, probability
of failures and recomputations, and fairness based evic-
tions. Restarted tasks have their execution times and fail-
ure probabilities sampled from the same distribution of
tasks in their phase. The simulator also mimics the job
workflow including semantics like barriers before phases,
the permissible concurrent slots per phase and the in-
put/output relationships between phases. It mimics clus-
ter characteristics like machine failures, network conges-
tion and availability of computation slots. For the net-
work, it uses a fluid model rather than simulating indi-
vidual packets. Doing the latter, at petabyte scale, is out
of scope for this work.
Compared Schemes: Our results on the production clus-
ter uses the current Dryad implementation as the base-
line ($6.2). It contains state-of-the-art outlier mitigation
strategies and runs thousands of jobs daily.
Our simulator performs a wider and detailed com-
parison. It compares Mantri with the outlier mitigation
strategies in Hadoop [1], Dryad [13], Map-Reduce [11],
LATE [20], and a modified form of LATE that acts on
stragglers early in the phase. As the current Dryad build
already has modules for straggler mitigation, we com-
pare all of these schemes to a baseline that does not miti-
gate any stragglers ($6.3). On the other hand, since these
schemes do not do network-aware placement or recom-
pute mitigation, we use the current Dryad implementa-
tion itself as their baseline ($6.4 and $6.5).
We also compare Mantri against some ideal bench-
marks. NoSkew mimics the case when all tasks in a phase
take the same amount of time, set to the average over
the observed task durations. NoSkew + ChopTail goes
even further, it removes the worst quartile of the observed
durations, and sets every task to the average of remain-
ing durations. IdealReduce assumes perfect up-to-date
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
100 100
ov
z 80 — 80
F 60 5 60
8 ce
x 40 x 40
a a
Oo 20 oOo 2
32.19
o 4 (32.1%) —e4 1 1 1 1
0 20 40 60 80 1 -20 0 20 40 60 80
% Reduction in Completion Time % Reduction in Job Resources
(a) Completion Time (b) # Resource Usage
Figure 12: Evaluation of Mantri as the default build for all jobs
on the production cluster for twenty-five days.
knowledge of available bandwidths and places reduce
tasks accordingly. IdealRecompute uses future knowledge
of which tasks will have their inputs recomputed and pro-
tects those inputs.
Metrics: As our primary metrics, we use the reduction in
completion time and resource usage*, where
Current — Modified
(4)
Reduction =
Current
Summary: Our results are summarized as follows:
e In live deployment in the production cluster Mantri
sped up the median job by 32%. 55% of the jobs ex-
perienced a net reduction in resources used. Further
Mantri’s network-aware placement reduced the com-
pletion times of typical reduce phases by 31%.
e Simulations driven from production logs show that
Mantri’s restart strategy reduces the completion time of
phases by 21% (and 42%) at the 50°” (and 75*”) per-
centile. Here, Mantri’s reduction in completion time
improves on Hadoop by 3.1x while using fewer re-
sources than Map-Reduce, each of which are the cur-
rent best on those respective metrics.
e Mantri’s network-aware placement of tasks speeds up
half of the reduce phases by at least 60% each.
e Mantri reduces the completion times due to recomputa-
tions of jobs that constitute 25% (or 50%) of the work-
load by at least 40% (or 20%) each while consuming
negligible extra resources.
6.2 Deployment Results
Jobs in the Wild: We compare one month of jobs in the
Bing production cluster that ran after Mantri was turned
live with runs of the same job(s) on earlier builds. We
use only those recurring jobs that have roughly similar
amounts of input and output across runs. Figure 12(a)
plots the CDF of the improvement in completion time.
The y axes weighs each job by the total time its tasks take
to run since improvement on larger jobs adds more value
°A reduction of 50% implies that the property in question, com-
pletion time or resources used, decreases by half. Negative values of
reduction imply that the modification uses more resources or takes
longer.
USENIX Association
USENIX Association
w
o
S
So
a
o
w
So
w
o
N
o
Be
o
o
Job Resources
% Reduction in
Completion Time
8
% Reduction in
o
Word Table Group Grep °
Count Join By
(a) Completion Time
Word Table Group Grep
Count Join By
(b) # Resource Usage
Figure 13: Comparing Mantri’s straggler mitigation with the
baseline implementation on a production cluster of thousands
of servers for the four representative jobs.
% reduction in completion time
avg min max
Phase 21.5 28.4 34.2
Job 12.6 7.0 19.2
Table 2: Comparing Mantri’s network-aware spread of tasks
with the baseline implementation on a production cluster of
thousands of servers.
to the cluster. Jobs that occupy the cluster for half the time
sped up by at least 32.1%. Figure 12(b) shows that 55% of
jobs see a reduction in resource consumption while the
others use up a few extra resources. These gains are due
to Mantri’s ability to detect outliers early and accurately.
The success rate of Mantri’s copies, i.e., the fraction of time
they finish before the original copy, improves by 2.8x over
the earlier build. At the same time, Mantri expends fewer
resources, it starts .47x fewer copies. Further, Mantri acts
early, over 50% of its copies are started before the original
task has completed 42% of its work as opposed to 77%
with the earlier build.
Straggler Mitigation: To cross-check the above results
on standard jobs, we ran four prototypical jobs with and
without Mantri twenty times each. Figure 13 shows that
job completion times improve by roughly 25% and re-
source usage falls by roughly 10%. The histograms plot
the average reduction, error bars are the 10” and 90°”
percentiles of samples. Further, we logged all the progress
reports for these jobs. We find that Mantri’s predictor,
based on reports from the recent past, estimates tyem, to
within a 2.9% error of the actual completion time.
Placement of Tasks: To evaluate Mantri’s network-aware
spreading of reduce tasks, we ran Group By, a job with a
long-running reduce phase, ten times on the production
cluster. Table 2 shows that the reduce phase’s completion
time reduces by 28.4% on average causing the job to speed
up by an average of 12.6%. To understand why, we mea-
sure the spread of tasks, i-e., the ratio of the number of
concurrent reduce tasks to the number of racks they ran
in. High spread implies that some racks have more tasks
which interfere with each other while other racks are idle.
Mantri’s spread is 1.5 compared to 5.5 for the earlier build.
To compare against alternative schemes and to piece
apart gains from the various algorithms in Mantri, we
»
o
o
©
o
--Dryad
60
—Hadoop
> LATE
20 ---MapReduce
—Mantri
CDF % Phase Duration
° s
-20 O 20 40 60 80 100
% Reduction in Completion Time
(a) Change in Completion Time
100
80 --Dryad
60 —Hadoop
°» LATE
---MapReduce
—Mantri
N
o
CDF % Phase Duration
° 8
-40 -20 0 20 40 60 80 100
% Reduction in Resource Usage
(b) Change in Resource Usage
Figure 14: Comparing straggler mitigation strategies. Mantri
provides a greater speed-up in completion time while using
fewer resources than existing schemes.
present results from the trace-driven simulator.
6.3 Can Mantri mitigate stragglers?
Figure 14 compares straggler mitigation strategies in their
impact on completion time and resource usage. The y-
axes weighs phases by their lifetime since improving the
longer phases improves cluster efficiency. The figures plot
the cumulative reduction in these metrics for the 210K
phases in Table 1 with each repeated thrice. For this sec-
tion, our common baseline is the scheduler that takes no
action on outliers. Recall from §6.1 that the simulator re-
plays the task durations and the anomalies observed in
production.
Figures 14(a) and 14(b) show that Mantri improves
completion time by 21% and 42% at the 50°” and 75'”
percentiles and reduces resource usage by 3% and 7% at
these percentiles. From Figure 14(a), at the 50°” per-
centile, Mantri sped up phases by an additional 3.1x over
the 6.9% improvement of Hadoop, the next best scheme.
To achieve the smaller improvement Hadoop uses 15.9%
more resources (Fig. 14(b)). Map-Reduce and Dryad
have no positive impact for 80% and 50% of the phases
respectively. Up to the 30” percentile Dryad increases
the completion time of phases. LATE is similar in its time
improvement to Hadoop but uses fewer resources.
The reason for poor performance is that they miss out-
liers that happen early in the phase and by not knowing
the true causes of outliers, the duplicates they schedule are
mostly not useful. Mantri and Dryad schedule .2 restarts
per task for the average phase (.06 and .56 for LATE and
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) = 275
276
b
°
Ss
b
°
Ss
90
oS
80
7 LATE
++ LATE + Early
a
o
60
40 «+ LATE + Early
—LATE
N
o
20
CDF % Phase Duration
° &
CDF % Phase Duration
o
-20 -10 0 10 20 30 40
% Reduction in Resource Usage
(b) Resources
-20 -10 0
% Reduction in Completion Time
(a) Time
10 20 30 40
Figure 15: Extending LATE to speculate early results in worse
performance
100
5 100 7_ Noskew 5 errr SS Sanaa
S So Ff LW
5 -» NoSkew + ChopTail = e 80
eee 3
a aoe" Q 60
g g
J «o 40 «> NoSkew + ChopTail
= =
pe & --NoSkew
XS as F
ae Fee —Mantri
a T T T T 1g i i T T 1
VY, oO
-10 0 10 20 30 40 50 = -10 0 10 20 30 40 #450
% Reduction in Resource Usage
% Reduction in Completion Time
(b) Resources
(a) Time
Figure 16: Mantri is on par with an ideal NoSkew benchmark
and slightly worse than NoSkew+Chop Tail (see end of $6.3)
100 -5 —Start
—Equal
BO Tee Mantri
60 4 --ldealReduce
gives loe
CDF % Phase Duration
N PS
o o
0 20 40 60 80
% Reduction in Completion Time
100
Figure 17: By being network aware, Mantri speeds up the me-
dian reduce phase by 60% over the current placement.
Hadoop). But, Mantri’s restarts have a success rate of 70%
compared to the 15% for LATE. The other schemes have
lower success rates.
While the insight of early action on stragglers is valu-
able, it is nonetheless non trivial. We evaluate this in Fig-
ures 15(a) and 15(b) that present a form of LATE that
is identical in all ways except that it addresses stragglers
early. We see that addressing stragglers early increases
completion time up to the 40°” percentile, uses more re-
sources and is worse than vanilla LATE. Being resource
aware is crucial to get the best out of early action ($5.1).
Finally, Fig. 16 shows that Mantri is on par with the ideal
benchmark that has no variation in tasks, NoSkew, and is
slightly worse than the variant that removes all durations
in the top quartile, NoSkew+ChopTail. The reason is that
Mantri’s ability to substitute long running tasks with their
faster copies makes up for its inability to act with perfect
future knowledge of which tasks straggle.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
6.4 Does Mantri improve placement?
Figure 17 plots the reduction in completion time due to
Mantri’s placement of reduce tasks as a CDF over all re-
duce phases in the dataset in Table 1. As before, the y-
axes weighs phases by their lifetime. The figure shows that
Mantri provides a median speed up of 59% or a 2.5x im-
provement over the current implementation.
The figure also compares Mantri against strategies that
estimate available bandwidths differently. The IdealRe-
duce strategy tracks perfectly the changes in available
bandwidth of links due to the other jobs in the cluster. The
Equal strategy assumes that the available bandwidths are
equal across all links whereas Start assumes that the avail-
able bandwidths are the same as at the start of the phase.
We see a partial order between Start and Equal (the two
solid lines). Short phases are impacted by transient dif-
ferences in the available bandwidths and Start is a good
choice for these phases. However, these differences even
out over the lifetime of long phases for whom Equal works
better. Mantri is a hybrid of Start and Equal. It achieves a
good approximation of IdealReduce without re-sampling
available bandwidths.
To capture how Mantri’s placement differs from Dryad,
Figure 18 plots the ratio of the throughput obtained by the
median task in each reduce phase to that obtained by the
slowest task. With Mantri, this ratio is 1.05 at median and
never larger than 2. In contrast, with Dryad’s policy of
placing tasks at the first available slot, this ratio is 5.25 (or
14.33) at the 50°” (or 75") percentile. Note that duplicat-
ing tasks that are delayed due to network congestion with-
out considering the available bandwidths or where other
tasks are located would be wasteful.
6.5 Does Mantri help with recomputations?
The best possible protection against loss of output would
(a) eliminate all the increase in job completion time due
to tasks waiting for their inputs to be recomputed and (b)
do so with little additional cost. Mantri approximates both
goals. Fig. 19 shows that Mantri achieves parity with Ideal-
Recompute. Recall that IdealRecompute has perfect future
knowledge of loss. The improvement in job completion
time is 20% (40%) at the 50” (75*”) percentile.
The reason is that Mantri’s policy of selective replica-
tion is both accurate and biased towards the more expen-
sive recomputations. The probability that task output that
was replicated will be used because the original data be-
comes unavailable is 84%. Similarly, the probability that a
pre-computation becomes useful is 76%, which increases
to 93% if pre-computations are triggered only when two
recomputations happen at a machine in quick succes-
sion. Figure 20 shows the complementary contributions
from replication and pre-computation- each contribute
USENIX Association
USENIX Association
< 100
2
8 80
Ss
A 60
® 40 —Dryad
“nn .
8 ¢»Mantri
i 20
xs 0
La
7 1 6 11 16 21 26 31
Ratio of median to slowest
read throughputs of tasks
Figure 18: Unlike Dryad, Mantri’s placement provides more
consistent throughput to tasks in reduce phases.
n
b
oo
— a)
a
o
” —Mantri
++ IdealRecompute
N
o
CDF % Job Duratio
° &
0 20 40 60 80 100
% Reduction in Completion Time
Figure 19: By probabilistically replicating task output and
recomputing lost data before it is needed Mantri speeds up
jobs by an amount equal to the ideal case of no data loss.
4100 ~~—~Pre-computation
<
Oo Replication 1
% 80 4--Mantri i
5 1
© 60 I
2 Poe soto aeeeh eo
040 4 fi. eee, '
ra i" oer I I!
R20 |
ra} (25%)! 1(53%) (78%)
Oo 0 1 1
0 20 40 60 80 100
% Recomputes Eliminated
a 20: Fraction of recomputations that are eliminated due
to Mantri’s recomputation mitigation strategy, along with indi-
vidual contributions from replication and pre-computation.
roughly 66% and 33% to the total. Cumulatively, the fig-
ure shows that Mantri eliminates 78% of recomputations
for the median job. We note that Mantri ignores 75% of
the recomputations in the bottom quartile of jobs since
their impact on job completion time is small.
Fig. 21(a) shows that the extra network traffic due to
replication is (overall negligible and) comparable to Ide-
alReduce. Mantri sometimes replicates more data than the
ideal, and at other times misses some tasks that should be
replicated. Fig. 21(b) shows that pre-computations take
only a few percentage extra resources.
7 Related Work
Much recent work focuses on large scale data parallel
computing. Following on the Map-Reduce [11] paper,
there has been work in improving workflows [1, 13], lan-
guage design [8, 27], and fair schedulers [18]. Our work
B
°
So
80
60
a
So
—Replication
++ ldealRecompute
40 40
20 20
CDF % of Total Traffic
0 +>
005 115 2 25 3
Increase in Cluster Resource(%)
(b) Cost: Cluster Time
o
CDF % Cluster Resources
pp J
0 02 04 06 08
Increase in Traffic (%)
(a) Cost: Network Traffic
Figure 21: The cost to protect against recomputes is fewer than
a few percentage points in both the extra traffic on the network
and cluster time for pre-computation.
here takes the next step of understanding how such pro-
duction clusters behave and can be improved.
Run-time stragglers have been identified by past
work [11, 20]. However, we are the first to character-
ize the prevalence of stragglers in production and their
causes. By understanding the causes, addressing strag-
glers early and scheduling duplicates only when there is
a fair chance that the speculation saves both time and re-
sources, our approach provides a greater reduction in job
completion time while using fewer resources than prior
strategies that duplicate tasks towards the end of a phase.
Also, we uniquely avoid network hotspots and protect
against loss of task output, two further causes of outliers.
By only acting at the end ofa phase, current schemes [1,
11, 13] miss early outliers. They vary in the choice of
which tasks to duplicate. After a threshold number of
tasks have finished, Map-Reduce [11] duplicates all the
tasks that remain. Dryad [13] duplicates those that have
been running for longer than the 75th percentile of task
durations. After all tasks have started, Hadoop [1] uses
slots that free up to duplicate any task that has read less
data than the others, while LATE [20] duplicates only
those reading at a slow rate.
Though some recent proposals do away with capacity
over-subscription in data centers [3, 17], today’s networks
remain over-subscribed albeit at smaller levels. It is com-
mon to place tasks near their input (same machine, rack
etc.) for map and at the first free slot for reduce [1, 11, 13].
Our approach to eliminate outliers by a network-aware
placement is orthogonal to recent work that packs tasks
requiring different resources on to a machine [25], or
trades-off fairness with efficiency [18]. Quincy accounts
for capacity but not for runtime variations in bandwidth
due to competition from other tasks.
ISS [15] protects intermediate data by replicating
locally-consumed data. In particular, this does not in-
clude map output, since Hadoop transfers map output to
reduce tasks as it is produced. ISS’s replication strategy
runs the risk of being both wasteful (when very few ma-
chines are error-prone) and insufficient (when the trans-
fer of map output fails). In contrast, Mantri presents a
broader solution that (a) replicates task output based on
the probability of data loss and the recursive cost of re-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 710) 277
278
computing inputs and (b) pre-computes lost data.
The Map-Reduce paradigm is similar to parallel
databases in its goal of analyzing large data [22] and to
dedicated HPC clusters and parallel programs [16] by
presenting similar optimization opportunities. In the
context of multiple processors, studies have been done on
the classic problem of dynamic task scheduling [4, 6] as
well as task duplication [24]. Star-MPI [2] adapts param-
eters like network topology between a set of communi-
cating processors by observing performance over time.
Prior work has also focused on modeling and optimiz-
ing the communication in parallel programs [10, 19, 21]
that have one-to-all or all-to-all traffic, i-e., where ev-
ery receiver processes all of the output of tasks in earlier
stages. In the context of the many-to-many traffic, typical
of Map-Reduce, we present practical techniques for band-
width estimation and task placement that realizes near-
optimal performance.
8 Conclusion
Mantri delivers effective mitigation of outliers in Map-
Reduce networks. It is motivated by, what we believe is,
the first study of a large production Map-Reduce clus-
ter. The root of Mantri’s advantage lies in integrating
static knowledge of job structure and dynamically avail-
able progress reports into a unified framework that iden-
tifies outliers early, applies cause-specific mitigation and
does so only if the benefit is higher than the cost. In our
implementation on a cluster of thousands of servers, we
find Mantri to be highly effective.
Outliers are an inevitable side-effect of parallelizing
work. They hurt Map-Reduce networks more due to the
structure of jobs as graphs of dependent phases that pass
data from one to the other. Their many causes reflect the
interplay between the network, storage and, computation
in Map-Reduce. Current systems shirk this complexity
and assume that a duplicate would speed things up. Mantri
embraces it to mitigate a broad set of outliers.
Acknowledgments- For feedback on drafts, we thank
members of the RAD lab, the Cosmos product group and
the OSDI reviewers. Alexei Polkhanov and Juhan Lee
were invaluable in taking Mantri to production clusters.
References
[1] Hadoop distributed filesystem. http://hadoop.apache.org.
[2] A. Faraj, X. Yuan, D. Lowenthal. STAR-MPI: Self Tuned Adap-
tive Routines for MPI Collective Operations. In SC, 2006.
[3] A. Greenberg, N. Jain, S. Kandula, C. Kim, P. Lahiri, D. A. Maltz,
P. Patel, and S. Sengupta. VL2: A Scalable and Flexible Data
Center Network. In SIGCOMM, 2009.
[4] I. Ahmad and M. K. Dhodhi. Semi-distributed load balancing
for massively parallel multicomputer systems. In IEEE TSE.,
1991.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
[5]
[6]
[7]
[8]
[9]
10]
11]
12]
13]
14]
16]
17]
18]
19]
20]
21]
22]
23]
24]
25]
26]
27]
28]
G. Ananthanarayanan, S. Kandula, A. Greenberg, I. Stoica, and
Y. Lu. Reigning in the outliers in map-reduce clusters. Technical
Report MSR-TR-2010-69, Microsoft Research, 2010.
B. Ucar, C. Aykanat, K. Kaya, M. Ikinci. Task assignment in Het-
erogeneous Computing Systems. In JPDC, 2006.
L. N. Bairavasundaram, G. R. Goodson, B. Schroeder, A. C.
Arpaci-Dusseau, and R. H. Arpaci-Dusseau. An analysis of data
corruption in the storage stack. In FAST, 2008.
R. Chaiken, B. Jenkins, P. Larson, B. Ramsey, D. Shakib,
S. Weaver, and J. Zhou. SCOPE: Easy and Efficient Parallel Pro-
cessing of Massive Datasets. In VLDB, 2008.
T. Condie, N. Conway, P. Alvaro, J. M. Hellerstein, K. Elmleegy,
and R. Sears. Mapreduce online. In NSDI, 2010.
D. Culler et al. LogP: Towards a Realistic Model of Parallel Com-
putation. In SIGPLAN PPoPP, 1993.
J. Dean and S. Ghemawat. Mapreduce: Simplified data process-
ing on large clusters. In OSDI, 2004.
R. L. Graham. Bounds on multiprocessing timing anomalies.
SIAM Journal on Applied Mathematics, 17(2), 1969.
M. Isard et al. Dryad: Distributed Data-parallel Programs from
Sequential Building Blocks. In Eurosys, 2007.
S. Kandula, D. Katabi, B. Davie, and A. Charny. Walking the
Tightrope: Responsive Yet Stable Traffic Engineering. In SIG-
COMM, 2005.
S. Ko, I. Hoque, B. Cho, and I. Gupta. Making cloud intermediate
data fault-tolerant. In SOCC, 2010.
A. Krishnamurthy and K. Yelick. Analysis and optimizations for
shared address space programs. JPDC, 1996.
M. Al-Fares, A. Loukissas, and A. Vahdat. A Scalable, Commod-
ity Data Center Network Architecture. In SIGCOMM, 2008.
M. Isard, V. Prabhakaran, J. Currey, U. Wieder, K. Talwar, A.
Goldberg. Quincy: Fair scheduling for distributed computing
clusters. In SOSP, 2009.
M. Lauria and A. Chien. MPI-FM: High Performance MPI on
Workstation Clusters. In JPDC, 1997.
M. Zaharia, A. Konwinski, A. D. Joseph, R. Katz, I. Stoica. Im-
proving MapReduce Performance in Heterogeneous Environ-
ments. In OSDI, 2008.
P. Patarasuk, A. Faraj, X. Yuan. Pipelined Broadcast on Ethernet
Switched Clusters. In IEEE IPDPS, 2006.
A. Pavlo, E. Paulson, A. Rasin, D. J. Abadi, D. J. DeWitt, S. R.
Madden, and M. Stonebraker. A comparison of approaches to
large scale data analysis. In SIGMOD, 2009.
S. Kandula, S. Sengupta, A. Greenberg, P. Patel, R. Chaiken. Na-
ture of Datacenter Traffic: Measurements and Analysis. In IMC,
2009.
S. Manoharan. Effect of task duplication on assignment of de-
pendency graphs. In Parallel Comput., 2001.
T. Sandholm and K. Lai. Mapreduce optimization using regu-
lated dynamic prioritization. In SIGMETRICS, 2009.
Y. Kwon, M. Balazinska, B. Howe, J. Rolia. Skew-Resistant Par-
allel Processing of Feature-Extracting Scientific User-Defined
Functions. In SOCC, 2010.
Y. Yu, M. Isard, D. Fetterly, M. Budiu, U. Erlingsson, P. K. Gunda,
J. Currey. DryadLINQ: A System for General-Purpose Data-
Parallel Computing Using a High-Level Language. In OSDI,
2008.
Y. Yu, P. K. Gunda, and M. Isard. Distributed Aggregation for
Data-Parallel Computing: Interfaces, Impl. In SOSP, 2009.
USENIX Association
USENIX Association
Transactional Consistency and Automatic Management in an
Application Data Cache
Dan R. K. Ports Austin T. Clements
Irene Zhang
Samuel Madden Barbara Liskov
MIT CSAIL
txcache@csail.mit.edu
Abstract
Distributed in-memory application data caches like mem-
cached are a popular solution for scaling database-driven
web sites. These systems are easy to add to existing de-
ployments, and increase performance significantly by re-
ducing load on both the database and application servers.
Unfortunately, such caches do not integrate well with
the database or the application. They cannot maintain
transactional consistency across the entire system, vio-
lating the isolation properties of the underlying database.
They leave the application responsible for locating data
in the cache and keeping it up to date, a frequent source
of application complexity and programming errors.
Addressing both of these problems, we introduce a
transactional cache, TxCache, with a simple program-
ming model. TxCache ensures that any data seen within
a transaction, whether it comes from the cache or the
database, reflects a slightly stale but consistent snap-
shot of the database. TxCache makes it easy to add
caching to an application by simply designating func-
tions as cacheable; it automatically caches their results,
and invalidates the cached data as the underlying database
changes. Our experiments found that adding TxCache
increased the throughput of a web application by up to
5.2, only slightly less than a non-transactional cache,
showing that consistency does not have to come at the
price of performance.
1 Overview
Today’s web applications are used by millions of users
and demand implementations that scale accordingly. A
typical system includes application logic (often imple-
mented in web servers) and an underlying database that
stores persistent state, either of which can become a bot-
tleneck [1]. Increasing database capacity is typically a
difficult and costly proposition, requiring careful parti-
tioning or the use of distributed databases. Application
server bottlenecks can be easier to address by adding
more nodes, but this also quickly becomes expensive.
Application-level data caches, such as mem-
cached [24], Velocity/AppFabric [34] and NCache [25],
are a popular solution to server and database bottlenecks.
They are deployed extensively by well-known web ap-
plications like LiveJournal, Facebook, and MediaWiki.
These caches store arbitrary application-generated data in
a lightweight, distributed in-memory cache. This flexibil-
ity allows an application-level cache to act as a database
query cache, or to act as a web cache and cache entire
web pages. But increasingly complex application logic
and more personalized web content has made it more use-
ful to cache the result of application computations that
depend on database queries. Such caching is useful be-
cause it averts costly post-processing of database records,
such as converting them to an internal representation, or
generating partial HTML output. It also allows common
content to be cached separately from customized con-
tent, so that it can be shared between users. For example,
MediaWiki uses memcached to store items ranging from
translations of interface messages to parse trees of wiki
pages to the generated HTML for the site’s sidebar.
Existing caches like memcached present two chal-
lenges for developers, which we address in this paper.
First, they do not ensure transactional consistency with
the rest of the system state. That is, there is no way to
ensure that accesses to the cache and the database re-
turn values that reflect a view of the entire system at a
single point in time. While the backing database goes
to great length to ensure that all queries performed in a
transaction reflect a consistent view of the database, i.e. it
can ensure serializable isolation, it is nearly impossible
to maintain these consistency guarantees while using a
cache that operates on application objects and has no
notion of database transactions. The resulting anomalies
can cause incorrect information to be exposed to the user,
or require more complex application logic because the
application must be able to cope with violated invariants.
Second, they offer only a GET/PUT interface, plac-
ing full responsibility for explicitly managing the cache
with the application. Applications must assign names to
cached values, perform lookups, and keep the cache up
to date. This has been a common source of programming
errors in applications that use memcached. In particular,
applications must explicitly invalidate cached data when
the database changes. This is often difficult; identifying
every cached application computation whose value may
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) 279
280
have been changed requires global reasoning about the
application.
We address both problems in our transactional cache,
TxCache. TxCache provides the following features:
e transactional consistency: all data seen by the appli-
cation reflects a consistent snapshot of the database,
whether the data comes from cached application-
level objects or directly from database queries.
e access to slightly stale but nevertheless consistent
snapshots for applications that can tolerate stale data,
improving cache utilization.
e a simple programming model, where applications
simply designate functions as cacheable. The Tx-
Cache library then handles inserting the result of the
function into the cache, retrieving that result the next
time the function is called with the same arguments,
and invalidating cached results when they change.
To achieve these goals, TxCache introduces the follow-
ing noteworthy mechanisms:
e a protocol for ensuring that transactions see only
consistent cached data, using minor database modi-
fications to compute the validity times of database
queries, and attaching them to cache objects.
e a lazy timestamp selection algorithm that assigns a
transaction to a timestamp in the recent past based
on the availability of cached data.
e an automatic invalidation system that tracks each ob-
ject’s database dependencies using dual-granularity
invalidation tags, and produces notifications if they
change.
We ported the RUBiS auction website prototype and
MediaWiki, a popular web application, to use TxCache,
and evaluated it using the RUBiS benchmark [2]. Our
cache improved peak throughput by 1.5 — 5.2x depend-
ing on the cache size and staleness limit, an improvement
oonly slightly below that of a non-transactional cache.
The next section presents the programming model and
consistency semantics. Section 3 sketches the structure
of the system, and Sections 4—6 describe each component
in detail. Section 7 describes our experiences porting ap-
plications to TxCache, Section 8 presents a performance
evaluation, and Section 9 reviews the related work.
2 System and Programming Model
TxCache is designed for systems consisting of one or
more application servers that interact with a database
server. These application servers could be web servers
running embedded scripts (e.g. with mod_php), or dedi-
cated application servers, as with Sun’s Enterprise Java
Beans. The database server is a standard relational
database; for simplicity, we assume the application uses
a single database to store all of its persistent state.
TxCache introduces two new components, as shown in
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
Cache Database
ee
TxCache Library
Application
eee ee
Xe J
Data center
j=
Figure 1: Key components in a TxCache deployment.
The system consists of a single database, a set of cache
nodes, and a set of application servers. TxCache also
introduces an application library, which handles all inter-
actions with the cache server.
Figure |: a cache and an application-side cache library,
as well as some minor modifications to the database
server. The cache is partitioned across a set of cache
nodes, which may run on dedicated hardware or share
it with other servers. The application never interacts
with the cache servers; the TxCache library transparently
translates an application’s cacheable functions into cache
accesses.
2.1 Programming Model
Our goal is to make it easy to incorporate caching into a
new or existing application. Towards this end, TxCache
provides an application library with a simple program-
ming model, shown in Figure 2, based on cacheable func-
tions. Applications developers can cache computations
simply by designating functions to be cached.
Programs group their operations into transactions. Tx-
Cache requires applications to specify whether their trans-
actions are read-only or read/write by using either the
BEGIN-RO or BEGIN-RW function. Transactions are
ended by calling COMMIT or ABORT. Within a transac-
tion block, TxCache ensures that, regardless of whether
the application gets its data from the database or the
cache, it sees a view consistent with the state of the
database at a single point in time.
Within a transaction, operations can be grouped into
cacheable functions. These are actual functions in the pro-
gram’s code, annotated to indicate that their results can
be cached. A cacheable function can consist of database
queries and computation, and can also make calls to other
cacheable functions. To be suitable for caching, functions
USENIX Association
USENIX Association
@ BEGIN-RO(staleness) : Begin a read-only transac-
tion. The transaction sees a consistent snapshot
from within the past staleness seconds.
@ BEGIN-RW(): Begin a read/write transaction.
@ COMMIT() > timestamp : Commit a transaction and
return the timestamp at which it ran
e ABORT(): Abort a transaction
@ MAKE-CACHEABLE(fn) — cached-fn : Makes a
function cacheable. cached-fn is a new function
that first checks the cache for the result of an-
other call with the same arguments. If not found,
it executes fn and stores its result in the cache.
Figure 2: TxCache library API
must be pure, i.e. they must be deterministic, not have
side effects, and depend only on their arguments and the
database state. For example, it would not make sense to
cache a function that returns the current time. TxCache
currently relies upon programmers to ensure that they
only cache suitable functions, but this requirement could
also be enforced using static or dynamic analysis [14, 33].
Cacheable functions are essentially memoized. Tx-
Cache’s library provides a MAKE-CACHEABLE function
that takes an implementation of a cacheable function and
returns a wrapper function that can be called to take ad-
vantage of the cache. When called, the wrapper function
checks if the cache contains the result of a previous call
to the function with the same arguments that is consistent
with the current transaction’s snapshot. If so, it returns
it. Otherwise, it invokes the implementation function
and stores the returned value in the cache. With proper
linguistic support (e.g. Python decorators), marking a
function cacheable can be as simple as adding a tag to its
existing definition.
Our cacheable function interface is easier to use than
the GET/PUT interface provided by existing caches like
memcached. It does not require programmers to manually
assign keys to cached values and keep them up to date.
Although seemingly straightforward, this is nevertheless
a source of errors because selecting keys requires reason-
ing about the entire application and how the application
might evolve. Examining MediaWiki bug reports, we
found that several memcached-related MediaWiki bugs
stemmed from choosing insufficiently descriptive keys,
causing two different objects to overwrite each other [22].
In one case, a user’s watchlist page was always cached
under the same key, causing the same results to be re-
turned even if the user requested to display a different
number of days worth of changes.
TxCache’s programming model has another crucial
benefit: it does not require applications to explicitly up-
date or invalidate cached results when modifying the
database. Adding explicit invalidations requires global
reasoning about the application, hindering modularity:
adding caching for an object requires knowing every
place it could possibly change. This, too, has been a
source of bugs in MediaWiki [23]. For example, edit-
ing a wiki page clearly requires invalidating any cached
copies of that page. But other, less obvious objects must
be invalidated too. Once MediaWiki began storing each
user’s edit count in their cached USER object, it became
necessary to invalidate this object after an edit. This was
initially forgotten, indicating that identifying all cached
objects needing invalidation is not straightforward, espe-
cially in applications so complex that no single developer
is aware of the whole of the application.
2.2 Consistency Model
TxCache provides transactional consistency: all requests
within a transaction see a consistent view of the system
as of a specific timestamp. That is, requests see only
the effects of other transactions that committed prior to
that timestamp. For read/write transactions, TxCache
supports this guarantee by running them directly on the
database, bypassing the cache entirely. Read-only trans-
actions use objects in the cache, and TxCache ensures
that nevertheless they view a consistent state.
Most caches return slightly stale data simply because
modified data does not reach the cache immediately. Tx-
Cache goes further by allowing applications to specify an
explicit staleness limit to BEGIN-RO, indicating that that
the transaction can see a view of data from that time or
later. However, regardless of the age of the snapshot, each
transaction always sees a consistent view. This feature
is motivated by the observation that many applications
can tolerate a certain amount of staleness [18], and using
stale cached data can improve the cache’s hit rate [21].
Applications can specify their staleness limit on a per-
transaction basis. Additionally, when a transaction com-
mits, TxCache provides the user with the timestamp at
which it ran. Together, these can be used to avoid anoma-
lies. For example, an application can store the timestamp
of a user’s last transaction in its session state, and use that
as a Staleness bound so that the user never observes time
moving backwards. More generally, these timestamps
can be used to ensure a causal ordering between related
transactions [20].
We chose to have read/write transactions bypass the
cache entirely so that TxCache does not introduce new
anomalies. The application can expect the same guaran-
tees (and anomalies) of the underlying database. For ex-
ample, if the underlying database uses snapshot isolation,
the system will still have the same anomalies as snap-
shot isolation, but TxCache will never introduce snapshot
isolation anomalies into the read/write transactions of a
system that does not use snapshot isolation. Our model
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI’10) 281
282
could be extended to allow read/write transactions to read
information from the cache, if applications are willing
to accept the risk of anomalies. One particular challenge
is that read/write transactions typically expect to see the
effects of their own updates, while these cannot be made
visible to other transactions until the commit point.
3 System Architecture
In order to present an easy-to-use interface to application
developers, TxCache needs to store cached data, keep it
up to date, and ensure that data seen by an application is
transactionally consistent. This section and the following
ones describe how it achieves this using cache servers,
modifications to the database, and an application-side
library. None of this complexity, however, is visible to
the application, which sees only cachable functions.
An application running with TxCache accesses infor-
mation from the cache whenever possible, and from the
database on a cache miss. To ensure it sees a consistent
view, TxCache uses versioning. Each database query
has an associated validity interval, describing the range
of time over which its result was valid, which is com-
puted automatically by the database. The TxCache li-
brary tracks the queries that a cached value depends on,
and uses them to tag the cache entry with a validity inter-
val. Then, the library provides consistency by ensuring
that, within each read-only transaction, it only retrieves
values from the cache and database that were valid at
the same time. Thus, each transaction effectively sees a
snapshot of the database taken at a particular time, even
as it accesses data from the cache.
Section 4 describes how the cache is structured, and de-
fines how a cached object’s validity interval and database
dependencies are represented. Section 5 describes how
the database is modified to track query validity intervals
and provide invalidation notifications when a query’s re-
sult changes. Section 6 describes how the library tracks
dependencies for application objects, and selects consis-
tent values from the cache and database.
4 Cache Design
TxCache stores cached data in RAM on a number of
cache servers. The cache presents a hash table interface:
it maps keys to associated values. Applications do not
interact with the cache directly; the TxCache library trans-
lates the name and arguments of a function call into a
hash key, and checks and updates the cache itself.
Data is partitioned among cache nodes using a consis-
tent hashing approach [17], as in peer-to-peer distributed
hash tables [31, 35]. Unlike DHTs, we assume that the
system is small enough that every application node can
maintain a complete list of cache servers, allowing it to
immediately map a key to the responsible node. This
list could be maintained by hand in small systems, or
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
Now
Key 2 ——_| _
Timestamp «—+{—+—_1—_+—_ +1 +_ 111+ >
45 50 55
Figure 3: An example of versioned data in the cache at
one point in time. Each rectangle is a version of a data
item. For example, the data for key 1 became valid with
commit 51 and invalid with commit 53, and the data for
key 2 became valid with commit 46 and is still valid.
using a group membership service [10] in larger or more
dynamic environments.
4.1 Versioning
Unlike a simple hash table, our cache is versioned. In
addition to its key, each entry in the cache is tagged with
its validity interval, as shown in Figure 3. This interval is
the range of time at which the cached value was current.
Its lower bound is the commit time of the transaction
that caused it to become valid, and its upper bound is the
commit time of the first subsequent transaction to change
the result, making the cache entry invalid. The cache
can store multiple cache entries with the same key; they
will have disjoint validity intervals because only one is
valid at any time. Whenever the TxCache library puts
the result of a cacheable function call into the cache, it
includes the validity interval of that result (derived using
information obtained from the database).
To look up a result in the cache, the TxCache library
sends both the key it is interested in and a timestamp
or range of acceptable timestamps. The cache server re-
turns a value consistent with the library’s request, i.e. one
whose validity interval intersects the given range of ac-
ceptable timestamps, if any exists. The server also returns
the value’s associated validity interval. If multiple such
values exist, the cache server returns the most recent one.
When a cache node runs out of memory, it evicts old
cached values to free up space for new ones. Cache
entries are never pinned and can always be discarded; if
one is later needed, it is simply a cache miss. A cache
eviction policy can take into account both the time since
an entry was accessed, and its staleness. Our cache server
uses a least-recently-used replacement policy, but also
eagerly removes any data too stale to be useful.
4.2 Invalidation Tags and Streams
When an object is inserted into the cache, it can be flagged
as still-valid if it reflects the latest state of the database,
like Key 2 in Figure 3. For such objects, the database
USENIX Association
USENIX Association
provides invalidation notifications when they change.
Every still-valid object has an associated set of inval-
idation tags that describe which parts of the database
it depends on. Each invalidation tag has two parts: a
table name and an optional index key description. The
database identifies the invalidation tags for a query based
on the access methods used to access the database. A
query that uses an index equality lookup receives a two-
part tag, e.g. a search for users with name Alice would
receive tag USERS:NAME=ALICE. A query that performs
a sequential scan or index range scan has a wildcard for
the second part of the tag, e.g. USERS:«. Wildcard invali-
dations are expected to be very rare because applications
typically try to perform only index lookups; they exist
primarily for completeness. Queries that access multiple
tables or multiple keys in a table receive multiple tags.
The object’s final tag set will have one or more tags for
each query that the object depends on.
The database distributes invalidations to the cache as
an invalidation stream. This is an ordered sequence of
messages, one for each update transaction, containing the
transaction’s timestamp and all invalidation tags that it
affected. Each message is delivered to all cache nodes by
a reliable application-level multicast mechanism [10], or
by link-level broadcast if possible. The cache servers pro-
cess the messages in order, truncating the validity interval
for any affected object at the transaction’s timestamp.
Using the same transaction timestamps to order cache
entries and invalidations eliminates race conditions that
could occur if an invalidation reaches the cache server
before an item is inserted with the old value. These race
conditions are a real concern: MediaWiki does not cache
failed article lookups, because a negative result might
never be removed from the cache if the report of failure
is stale but arrived after its corresponding invalidation.
For cache lookup purposes, items that are still valid are
treated as though they have an upper validity bound equal
to the timestamp of the last invalidation received prior to
the lookup. This ensures that there is no race condition
between an item being changed on the database and in-
validated in the cache, and that multiple items modified
by the same transaction are invalidated atomically.
5 Database Support
The validity intervals that TxCache uses in its cache
are derived from validity information generated by the
database. To make this possible, TxCache uses a modi-
fied DBMS that has similar versioning properties to the
cache. Specifically, it can run queries on slightly stale
snapshots, and it computes validity intervals for each
query result it returns. It also assigns invalidation tags to
queries, and produces the invalidation stream described
in Section 4.2.
Though standard databases do not provide these fea-
tures, we show they can be implemented by reusing the
same mechanisms that are used to implement multiver-
sion concurrency control techniques like snapshot isola-
tion. In this section, we describe how we modified an ex-
isting DBMS, PostgreSQL [29], to provide the necessary
support. The modifications are not extensive (under 2000
lines of code in our implementation). Moreover, they
are not Postgres-specific; the approach can be applied to
other databases that use multiversion concurrency.
5.1 Exposing Multiversion Concurrency
Because our cache allows read-only transactions to run
slightly in the past, the database must be able to perform
queries against a past snapshot of a database. This sit-
uation arises when a read-only transaction is assigned
a timestamp in the past and reads some cached data,
and then a later operation in the same transaction results
in a cache miss, requiring the application to query the
database. The database query must return results consis-
tent with the cached values already seen, so the query
must execute at the same timestamp in the past.
Temporal databases, which track the history of their
data and allow “time travel,” solve this problem but im-
pose substantial storage and indexing cost to support
complex queries over the entire history of the database.
What we require is much simpler: we only need to run a
transaction on a stale but recent snapshot. Our insight is
that these requirements are essentially identical to those
for supporting snapshot isolation [5], so many databases
already have the infrastructure to support them.
We modified Postgres to expose the multiversion stor-
age it uses internally to provide snapshot isolation. We
added a PIN command that assigns an ID to a read-only
transaction’s snapshot. When starting a new transaction,
the TxCache library can specify this ID using the new
BEGIN SNAPSHOTID syntax, creating a new transaction
that sees the same view of the database as the erstwhile
read-only transaction. The database state for that snap-
shot will be retained at least until it is released by the
UNPIN command. A pinned snapshot is identified by the
commit time of the last committed transaction visible to
it, allowing it to be easily ordered with respect to update
transactions and other snapshots.
Postgres is especially well-suited to this modifica-
tion because of its “no-overwrite” storage manager [36],
which already retains recent versions of data. Because
stale data is only removed periodically by an asyn-
chronous “vacuum cleaner’ process, the fact that we keep
data around slightly longer has little impact on perfor-
mance. However, our technique is not Postgres-specific;
any database that implements snapshot isolation must
have a way to keep a similar history of recent database
states, such as Oracle’s rollback segments.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) = 283
284
Query Timestamp
Tuple 4
Result Validity
Invalidity Mask
Invalidity Mask
Validity:Interval
Commits 43 44 45 46 47 48 49
'
Figure 4: Example of tracking the validity interval for a
read-only query. All four tuples match the query predi-
cate. Tuples 1 and 2 match the timestamp, so their inter-
vals intersect to form the result validity. Tuples 3 and 4
fail the visibility test, so their intervals join to form the in-
validity mask. The final validity interval is the difference
between the result validity and the invalidity mask.
5.2 Tracking Result Validity
TxCache needs the database server to provide the va-
lidity interval for every query result in order to ensure
transactional consistency of cached objects. Recall that
this is defined as the range of timestamps for which the
query would give the same results. Its lower bound is the
commit time of the most recent transaction that added,
deleted, or modified any tuple in the result set. It may
have an upper bound if a subsequent transaction changed
the result, or it may be unbounded if the result is still
current.
The validity interval is computed as the intersection
of two ranges, the result tuple validity and the invalidity
mask, which we track separately.
The result tuple validity is the intersection of the valid-
ity times of the tuples returned by the query. For example,
tuple 1 in Figure 4 was deleted at time 47, and tuple 2
was created at time 44; the result would be different be-
fore time 44 or after time 47. This interval is easy to
compute because multiversion concurrency requires that
each tuple in the database be tagged with the ID of its
creating transaction and deleting transaction (if any). We
simply propagate these tags throughout query execution.
If an operator, such as a join, combines multiple tuples to
produce a single result, the validity interval of the output
tuple is the intersection of its inputs.
The result tuple validity, however, does not completely
capture the validity of a query, because of phantoms.
These are tuples that did not appear in the result, but
would have if the query were run at a different timestamp.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
For example, tuple 3 in Figure 4 will not appear in the
results because it was deleted before the query timestamp,
but the results would be different if the query were run
before it was deleted. Similarly, tuple 4 is not visible
because it was created afterwards. We capture this effect
with the invalidity mask, which is the union of the va-
lidity times for all tuples that failed the visibility check,
i.e. were discarded because their timestamps made them
invisible to the transaction’s snapshot. Throughout query
execution, whenever such a tuple is encountered, its va-
lidity interval is added to the invalidity mask.
The invalidity mask is conservative because visibility
checks are performed as early as possible in the query
plan to avoid processing unnecessary tuples. Some of
these tuples might have been discarded anyway if they
failed the query conditions later in the query plan (per-
haps after joining with another table). While being con-
servative preserves the correctness of the cached results,
it might unnecessarily constrain the validity intervals of
cached items, reducing the hit rate. To ameloriate this
problem, we continue to perform the visibility check as
early as possible, but during sequential scans and index
lookups, we evaluate the predicate before the visibility
check. This differs from regular Postgres with respect to
sequential scans, where it evaluates the cheaper visibility
check first. Delaying the visibility checks improves the
quality of the invalidity mask, and incurs little overhead
for simple predicates, which are most common.
Finally, the invalidity mask is subtracted from the re-
sult tuple validity to give the query’s final validity in-
terval. This interval is reported to the TxCache library,
piggybacked on each SELECT query result; the library
combines these intervals to obtain validity intervals for
objects it stores in the cache.
5.3 Automating Invalidations
When the database executes a query and reports that its
validity interval is unbounded, i.e. the query result is still
valid, it assumes responsibility for providing an invalida-
tion when the result may have changed. At query time,
it must assign invalidation tags to indicate the query’s
dependencies, and at update time, it must notify the cache
of invalidation tags for objects that might have changed.
When a query is performed, the database examines the
query plan it generates. At the lowest level of the tree are
the access methods that obtain the data, e.g. a sequential
scan of a heap file, or a B-tree index lookup. For index
equality lookups, the database assigns an invalidation tag
of the form TABLE:KEY. For other types, it assigns a
wildcard tag TABLE:*. Each query may have multiple
tags; the complete set is returned along with the SELECT
query results.
When a read/write transaction modifies some tuples,
the database identifies the set of invalidation tags affected.
USENIX Association
USENIX Association
Each tuple added, deleted, or modified yields one inval-
idation tag for each index it is listed in. If a transaction
modifies most of a table, the database can aggregate multi-
ple tags into a single wildcard tag on TABLE:x. Generated
invalidation tags are queued until the transaction commits.
When it does, the database server passes the set of tags,
along with the transaction’s timestamp, to the multicast
service for distribution to the cache nodes, ensuring that
the invalidation stream is properly ordered.
5.4 Pincushion
TxCache needs to keep track of which snapshots are
pinned on the database, and which of those are within
a read-only transaction’s staleness limit. It also must
eventually unpin old snapshots, provided that they are
not used by running transactions. The DBMS itself could
be responsible for tracking this information. However, to
simplify implementation, and to reduce the overall load
on the database, we placed this functionality instead in a
lightweight daemon known as the pincushion (so named
because it holds the pinned snapshot IDs). It can be run
on the database host, on a cache server, or elsewhere.
The pincushion maintains a table of currently pinned
snapshots, containing the snapshot’s ID, the correspond-
ing wall-clock timestamp, and the number of running
transactions that might be using it. When the TxCache
library running on an application node begins a read-only
transaction, it requests from the pincushion all sufficiently
fresh pinned snapshots, e.g. those pinned in the last 30
seconds. The pincushion flags these snapshots as possibly
in use, for the duration of the transaction. If there are no
sufficiently fresh pinned snapshots, the TxCache library
starts a read-only transaction on the database, running on
the latest snapshot, and pins that snapshot. It then regis-
ters the snapshot’s ID and the wall-clock time (as reported
by the database) with the pincushion. The pincushion
also periodically scans its list of pinned snapshots, re-
moving any unused snapshots older than a threshold by
sending an UNPIN command to the database.
Though the pincushion is accessed on every transac-
tion, it performs little computation and is unlikely to form
a bottleneck. In all of our experiments, nearly all pin-
cushion requests received a response in under 0.2 ms,
approximately the network round-trip time. We have also
developed a protocol for replicating the pincushion to in-
crease its throughput, but it has yet to become necessary.
6 Cache Library
Applications interact with TxCache through its
application-side library, which keeps them blissfully
unaware of the details of cache servers, validity intervals,
invalidation tags and the like. It is responsible for as-
signing timestamps to read-only transactions, retrieving
values from the cache when cacheable functions are
called, storing results in the cache, and computing the
validity intervals and invalidation tags for anything it
stores in the cache.
In this section, we describe the implementation of the
TxCache library. For clarity, we begin with a simplified
version where timestamps are chosen when a transac-
tion begins and cacheable functions do not call other
cacheable functions. In Section 6.2, we describe a tech-
nique for choosing timestamps lazily to take better advan-
tage of cached data. In Section 6.3, we lift the restriction
on nested calls.
6.1 Basic Functionality
The TxCache library is divided into a language-
independent library that implements the core functional-
ity, and a set of bindings that implement language-specific
interfaces. Currently, we have only implemented bind-
ings for PHP, but adding support for other languages
should be relatively straightforward.
Recall from Figure 2 that the library’s interface is
simple: it provides the standard transaction commands
(BEGIN, COMMIT, and ABORT), and functions are desig-
nated as cacheable using a MAKE-CACHEABLE function
that takes a function and returns a wrapped function that
first checks for available cached values!.
When a transaction is started, the application specifies
whether it is read/write or read-only, and, if read-only, the
staleness limit. For a read/write transaction, the TxCache
library simply starts a transaction on the database server,
and passes all queries directly to it. At the beginning of a
read-only transaction, the library contacts the pincushion
to request the list of pinned snapshots within the staleness
limit, then chooses one to run the transaction at. If no
sufficiently recent snapshots exist, the library starts a new
transaction on the database and pins its snapshot.
The library can delay beginning an underlying read-
only transaction on the database (i.e. sending a BEGIN
SQL statement) until it actually needs to issue a query.
Thus, transactions whose requests are all satisfied from
the cache do not need to connect to the database at all.
When a cacheable function’s wrapper is called, the
library checks whether its result is in the cache. To do so,
it serializes the function’s name and arguments into a key
(a hash of the function’s code could also be used to handle
software updates). The library finds the responsible cache
server using consistent hashing, and sends it a LOOKUP
request. The request includes the transaction’s timestamp,
which any returned value must satisfy. If the cache returns
a matching result, the library returns it directly to the
program.
In the event of a cache miss, the library calls the
cacheable function’s implementation. As the cacheable
'In languages such as PHP that lack higher-order functions, the
syntax is slightly more complicated, but the concept is the same.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) = 285
286
function issues queries to the database, the library ac-
cumulates the validity intervals and invalidation tags re-
turned by these queries. The final result of the cacheable
function is valid at all times in the intersection of the
accumulated validity intervals. When the cacheable func-
tion returns, the library serializes its result and inserts
it into the cache, tagged with the accumulated validity
interval and any invalidation tags.
6.2 Choosing Timestamps Lazily
Above, we assumed that the library chooses a read-only
transaction’s timestamp when the transaction starts. Al-
though straightforward, this approach requires the library
to decide on a timestamp without any knowledge of what
data is in the cache or what data will be accessed. Lack-
ing this knowledge, it is not clear what policy would
provide the best hit rate.
However, the timestamp need not be chosen immedi-
ately. Instead, it can be chosen lazily based on which
cached results are available. This takes advantage of
the fact that each cached value is valid over a range of
timestamps: its validity interval. For example, consider
a transaction that has observed a single cached result x.
This transaction can still be serialized at any timestamp
in x’s validity interval. On the transaction’s next call to
a cacheable function, any cached value whose validity
interval overlaps x’s can be chosen, as this still ensures
there is at least one timestamp at which the transaction
can be serialized. As the transaction proceeds, the set of
possible serialization points narrows each time the trans-
action reads a cached value or a database query result.
Specifically, the algorithm proceeds as follows. When
a transaction begins, the library requests from the pin-
cushion all pinned snapshot IDs that satisfy its freshness
requirement. It stores this set as its pin set. The pin
set represents the set of timestamps at which the current
transaction can be serialized; it will be updated as the
cache and the database are accessed. The pin set also
initially contains a special ID, denoted x, which indicates
that the transaction can also be run in the present, on some
newly pinned snapshot. The pin set only contains x until
the first cacheable function in the transaction executes.
When the application invokes a cacheable function, the
library sends a LOOKUP request for the appropriate key,
but instead of indicating a single timestamp, it indicates
the bounds of the pin set (the lowest and highest times-
tamp, excluding x). The transaction can use any cached
value whose validity interval overlaps these bounds and
still remain serializable at one or more timestamps. The
library then reduces the transaction’s pin set by eliminat-
ing all timestamps that do not lie in the returned value’s
validity interval, since observing a cached value means
the transaction can no longer be serialized outside its
validity interval. This includes removing x from the pin-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
set because once the transaction has used cached data, it
cannot be run on a new, possibly inconsistent snapshot.
When the cache does not contain any entries that match
both the key and the requested interval, a cache miss
occurs. In this case, the library calls the cacheable func-
tion’s implementation, as before. When the transaction
makes its first database query, the library is finally forced
to select a specific timestamp from the pin set and BE-
GIN a read-only transaction on the database at the chosen
timestamp. If a non-* timestamp is chosen, the transac-
tion runs on that timestamp’s saved snapshot. If x is cho-
sen, the library starts a new transaction, pinning the latest
snapshot and reporting the pin to the pincushion. The pin
set is then reified: x is replaced with the newly-created
snapshot’s timestamp, replacing the abstract concept of
“the present time” with a concrete timestamp.
The library needs a policy to choose which pinned
snapshot from the pin set it should run at. Simply choos-
ing x if available, or the most recent timestamp otherwise,
biases transactions towards running on recent data, but
results in a very large number of pinned snapshots, which
can ultimately slow the system down. To avoid the over-
head of creating many snapshots, we used the following
policy: if the most recent timestamp in the pin set is
older than five seconds and x is available, then the library
chooses x in order to produce a new pinned snapshot;
otherwise it chooses the most recent timestamp.
During the execution of a cacheable function, the va-
lidity intervals of the queries that the function makes are
accumulated, and their intersection defines the validity
interval of the cacheable result, just as before. In addi-
tion, just like when a transaction observes values from
the cache, each time it observes query results from the
database, the transaction’s pin set is reduced by eliminat-
ing all timestamps outside the result’s validity interval, as
the transaction can no longer be serialized at these points.
If the transaction’s pin set still contains x, x is removed.
The validity interval of the cacheable function and pin
set of the transaction are two distinct but related notions:
the function’s validity interval is the set of timestamps
at which its result is valid, and the pin set is the set of
timestamps at which the enclosing transaction can be
serialized. The pin set always lies within the validity
interval, but the two may differ when a transaction calls
multiple cacheable functions in sequence, or performs
“bare” database queries outside a cacheable function.
6.2.1 Correctness
Lazy selection of timestamps is a complex algorithm,
and its correctness is not self-evident. The following two
properties show that it provides transactional consistency.
Invariant 1. All data seen by the application during
a read-only transaction is consistent with the database
USENIX Association
USENIX Association
State at every timestamp in the pin set, i.e. the transaction
can be serialized at any timestamp in the pin set.
Invariant | holds because any timestamps inconsistent
with data the application has seen are removed from the
pin set. The application sees two types of data: cached
values and database query results. Each is tagged with its
validity interval. The library removes from the pin set all
timestamps that lie outside either of these intervals.
Invariant 2. The pin set is never empty, i.e. the transac-
tion can always be serialized at some timestamp.
The pin set is initially non-empty: it contains the times-
tamps of all sufficiently-fresh pinned snapshots, if any,
and always x. So we must ensure that at least one times-
tamp remains every time the pin set shrinks, i.e. when a
result is obtained from the cache or database.
When a value is fetched from the cache, its validity
interval is guaranteed to intersect the transaction’s pin set
at at least one timestamp. The cache will only return an
entry with a non-empty intersection between its validity
interval and the bounds of the transaction’s pin set. This
intersection contains the timestamp of at least one pinned
snapshot: if the result’s validity interval lies partially
within and partially outside the bounds of the client’s pin
set, then either the earliest or latest timestamp in the pin
set lies in the intersection. If the result’s validity interval
lies entirely within the bounds of the transaction’s pin
set, then the pin set contains at least the timestamp of
the pinned snapshot from which the cached result was
originally generated. Thus, Invariant 2 continues to hold
even after removing from the pin set any timestamps that
do not lie within the cached result’s validity interval.
It is easier to see that when the database returns a
query result, the validity interval intersects the pin set
at at least one timestamp. The validity interval of the
query result must contain the timestamp of the pinned
snapshot at which it was executed, by definition. That
pinned snapshot was chosen by the TxCache library from
the transaction’s pin set (or it chose x, obtained a new
snapshot, and added it to the pin set). Thus, at least that
one timestamp will remain in the pin set after intersecting
it with the query’s validity interval.
6.3 Handling Nested Calls
In the preceding sections, we assumed that cacheable
functions never call other cacheable functions. However,
it is useful to be able to nest calls to cacheable functions.
For example, a user’s home page at an auction site might
contain a list of items the user recently bid on. We might
want to cache the description and price for each item as
a function of the item ID (because they might appear on
other user’s pages) in addition to the complete content of
the user’s page (because he might access it again).
Our implementation supports nested calls; this does
not require any fundamental changes to the approach
above. However, we must keep track of a separate cumu-
lative validity interval and invalidation tag set for each
cacheable function in the call stack. When a cached value
or database query result is accessed, its validity interval is
intersected with that of each function currently on the call
stack. As a result, a nested call to a cacheable function
may have a wider validity interval than its enclosing func-
tion, but not vice versa. This makes sense, as the outer
function might have seen more data than the functions it
calls (e.g. if it calls more than one cacheable function).
Similarly, any invalidation tags from the database are
attached to each function on the call stack, as each now
has a dependency on the data.
7 Experiences
We implemented all the components of TxCache, in-
cluding the cache server, database modifications to Post-
greSQL to support validity tracking and invalidations,
and the cache library with PHP language bindings.
One of TxCache’s goals is to make it easier to add
caching to a new or existing application. The TxCache
library makes it straightforward to designate a function
as cacheable. However, ensuring that the program has
functions suitable for caching still requires some effort.
Below, we describe our experiences adding support for
caching to the RUBiS benchmark and to MediaWiki.
7.1 Porting RUBiS
RUBiS [2] is a benchmark that implements an auction
website modeled after eBay where users can register
items for sale, browse listings, and place bids on items.
We ported its PHP implementation to use TxCache. Like
many small PHP applications, the PHP implementation
of RUBiS consists of 26 separate PHP scripts, written
in an unstructured way, which mainly make database
queries and format their output. Besides changing code
that begins and ends transactions to use TxCache’s inter-
faces, porting RUBiS to TxCache involved identifying
and designating cacheable functions. The existing im-
plementation had few functions, so we had to begin by
dividing it into functions; this was not difficult and would
be unnecessary in a more modular implementation.
We cached objects at two granularities. First, we
cached large portions of the generated HTML output
(except some headers and footers) for each page. This
meant that if two clients viewed the same page with the
same arguments, the previous result could be reused. Sec-
ond, we cached common functions such as authenticating
a user’s login, or looking up information about a user or
item by ID. Even these fine-grained functions were often
more complicated than an individual query; for example,
looking up an item requires examining both the active
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 710) 287
288
items table and the old items table. These fine-grained
cached values can be shared between different pages; for
example, if two search results contain the same item, the
description and price of that item can be reused.
We made a few modifications to RUBiS that were not
strictly necessary but improved its performance. To take
better advantage of the cache, we modified the code for
display lists of items to obtain details about each item
by calling our GET-ITEM cacheable function rather than
performing a join on the database. We also observed that
one interaction, finding all the items for sale in a particu-
lar region and category, required performing a sequential
scan over all active auctions, and joining it against the
users table. This severely impacted the performance of
the benchmark with or without caching. We addressed
this by adding a new table and index containing each
item’s category and region IDs. Finally, we removed a
few queries that were simply redundant.
7.2 Porting MediaWiki
We also ported MediaWiki to use TxCache, to better un-
derstand the process of adding caching to a more complex,
existing system. MediaWiki, which faces significant scal-
ing challenges in its use for Wikipedia, already supports a
variety of caches and replication systems. Unlike RUBiS,
it has an object-oriented design, making it easier to select
cacheable functions.
MediaWiki supports master-slave replication for the
database server. Because the slaves cannot process up-
date transactions and lag slightly behind the master, Me-
diaWiki already distinguishes the few transactions that
must see the latest state from the majority that can accept
the staleness caused by replication lag (typically 1-30
seconds). It also identifies read/write transactions, which
must run on the master. Although we used only one
database server, we took advantage of this classification
of transactions to determine which transactions can be
cached and which must execute directly on the database.
Most MediaWiki functions are class member functions.
Caching only pure functions requires being sure that func-
tions do not mutate their object. We cached only static
functions that do not access or modify global variables
(MediaWiki rarely uses global variables). Of the non-
static functions, many can be made static by explicitly
passing in any member variables that are used, as long
as they are only read. For example, almost every func-
tion in the TITLE class, which represents article titles, is
cacheable because a TITLE object is immutable.
Identifying functions that would be good candidates
for caching was more challenging, as MediaWiki is a
complex application with myriad features. Developers
with previous experience with the MediaWiki codebase
would have more insight into which functions were fre-
quently used. We looked for functions that were involved
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
in common requests like rendering an article, and mem-
ber functions of commonly-used classes. We focused on
functions that constructed objects based on data looked
up in the database, such as fetching a page revision. These
were good candidates for caching because we can avoid
the cost of one or more database queries, as well as the
cost of post-processing the data from the database to fill
the fields of the object. We also adapted existing caches
like the localization cache, which stores translations of
user interface messages.
8 Evaluation
We used RUBiS as a benchmark to explore the perfor-
mance benefits of caching. In addition to the PHP auction
site implementation described above, RUBiS provides a
client emulator that simulates many concurrent user ses-
sions: there are 26 possible user interactions (e.g. brows-
ing items by category, viewing an item, or placing a bid),
each of which corresponds to a transaction. We used
the standard RUBiS “bidding” workload, a mix of 85%
read-only interactions (browsing) and 15% read/write in-
teractions (placing bids) with a think time with negative
exponential distribution and 7-second mean.
We ran our experiments on a cluster of 10 servers, each
a Dell PowerEdge SC1420 with two 3.20 GHz Intel Xeon
CPUs, 2 GB RAM, and a Seagate ST3 1500341 AS 7200
RPM hard drive. The servers were connected via a gigabit
Ethernet switch, with 0.1 ms round-trip latency. One
server was dedicated to the database; it ran PostgreSQL
8.2.11 with our modifications. The others acted as front-
end web servers running Apache 2.2.12 with PHP 5.2.10,
or as cache nodes. Four other machines, connected via
the same switch, served as client emulators. Except as
otherwise noted, database server load was the bottleneck.
We used two different database configurations. One
configuration was chosen so that the dataset would fit
easily in the server’s buffer cache, representative of appli-
cations that strive to fit their working set into the buffer
cache for performance. This configuration had about
35,000 active auctions, 50,000 completed auctions, and
160,000 registered users, for a total database size about
850 MB. The larger configuration was disk-bound; it had
225,000 active auctions, 1 million completed auctions,
and 1.35 million users, for a total database size of 6 GB.
For repeatability, each test ran on an identical copy
of the database. We ensured the cache was warm by
restoring its contents from a snapshot taken after one hour
of continuous processing for the in-memory configuration
and one day for the disk-bound configuration.
For the in-memory configuration, we used seven hosts
as web servers, and two as dedicated cache nodes. For the
larger configuration, eight hosts ran both a web server and
a cache server, in order to make a larger cache available.
USENIX Association
USENIX Association
7000 T
6000
5000
4000
3000
2000
1000
0
64MB
T T
No consistency 1a"
TxCache —e—
Peak requests/sec
256MB 512MB
Cache size
768MB 1024MB
(a) In-memory database
800 T T T T T T T
TxCache —®—
700 No caching (baseline) =======
600
500
400
300
200
100
Peak requests/sec
0
1GB 2GB 3GB 4GB 5GB 6GB 7GB 8GB 9GB
Cache size
(b) Disk-bound database
Figure 5: Effect of cache size on peak throughput (30 second staleness limit)
100%
80%
60%
40%
Cache hit rate
20% F 4
0% 1 1 i
64MB 256MB 512MB 768MB
Cache size
1024MB
(a) In-memory database
100%
80%
60%
40%
Cache hit rate
20%
0% ! ! ! l ! ! !
1GB 2GB 3GB 4GB 5GB 6GB 7GB 8GB 9GB
Cache size
(b) Disk-bound database
Figure 6: Effect of cache size on cache hit rate (30 second staleness limit)
8.1 Cache Sizes and Performance
We evaluated RUBiS’s performance in terms of the peak
throughput achieved (requests handled per second) as
we varied the number of emulated clients. Our baseline
measurement evaluates RUBiS running directly on the
Postgres database, with TxCache disabled. This achieved
a peak throughput of 928 req/s with the in-memory config-
uration and 136 req/s with the disk-bound configuration.
We performed this experiment with both a stock copy
of Postgres, and our modified version. We found no
observable difference between the two cases, suggesting
our modifications have negligible performance impact.
Because the system already maintains multiple versions
to implement snapshot isolation, keeping a few more
versions around adds little cost, and tracking validity
intervals and invalidation tags simply adds an additional
bookkeeping step during query execution.
We then ran the same experiment with TxCache en-
abled, using a 30 second staleness limit and various cache
sizes. The resulting peak throughput levels are shown
in Figure 5. Depending on the cache size, the speedup
achieved ranged from 2.2x to 5.2x for the in-memory
configuration and from 1.8x to 3.2x for the disk-bound
configuration. The RUBiS PHP benchmark does not per-
form significant application-level computation; even so,
we see a 15% reduction in total web server CPU usage.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
Cache server load is low, with most CPU overhead in
kernel time, suggesting inefficiencies in the kernel’s TCP
stack as the cause. Switching to a UDP protocol might
alleviate some of this overhead [32].
Figure 6(a) shows that for the in-memory configura-
tion, the cache hit rate ranged from 27% to 90%, increas-
ing linearly until the working set size is reached, and
then growing slowly. Here, the cache hit rate directly
translates into a performance improvement because each
cache hit represents load (often many queries) removed
from the database. Interestingly, we always see a high
hit rate on the disk-bound database (Figure 6(b)) but it
does not always translate into a large performance im-
provement. This workload exhibits some very frequent
queries (e.g. looking up a user’s nickname by ID) that can
be stored in even a small cache, but are also likely to be
in the database’s buffer cache. It also has a large number
of data items that are each accessed rarely (e.g. the full
bid history for each item). The latter queries collectively
make up the bottleneck, and the speedup is determined
by how much of this data is in the cache.
8.2 Varying Staleness Limits
The staleness limit is an important parameter. By raising
this value, applications may be exposed to increasingly
stale data, but are able to take advantage of more cached
289
290
T T T T T
TxCache (in-memory DB, 512MB cache) —®—
TxCache (larger DB, 9GB cache) —i-— "|
No caching (baseline) ==="="== 4
Relative throughput
0 20 40 60 80 100 120
Staleness limit in seconds
Figure 7: Impact of staleness limit on peak throughput
data. An invalidated cache entry remains useful for the
duration of the staleness limit, which is valuable for val-
ues that change (and are invalidated) frequently.
Figure 7 compares the peak throughput obtained by
running transactions with staleness limits from 1 to 120
seconds. Even a small staleness limit of 5-10 seconds
provides a significant benefit. RUBiS has some objects
that are expensive to compute and have many data depen-
dencies (indexes of all items in particular regions with
their current prices). These objects are invalidated fre-
quently, but the staleness limit permits them to be used.
The benefit diminishes at around 30 seconds, suggesting
that the bulk of the data either changes infrequently (such
as information about inactive users or auctions), or is
accessed multiple times every 30 seconds (such as the
aforementioned index pages).
8.3 Costs of Consistency
A natural question is how TxCache’s guarantee of trans-
actional consistency affects its performance. We explore
this question by examining cache statistics and compar-
ing against other approaches.
We classified cache misses into four types, inspired by
the common classification for CPU cache misses:
e compulsory miss: the object was never in the cache
e staleness miss: the object has been invalidated, and
its staleness limit has been exceeded
© capacity miss: the object was previously evicted
@ consistency miss: some sufficiently fresh version of
the object was available, but it was inconsistent with
previous data read by the transaction
Figure 8 shows the breakdown of misses by type for four
different configurations. Our cache server unfortunately
cannot distinguish staleness and capacity misses. We see
that consistency misses are the least common by a large
margin. Consistency misses are rare, as items in the cache
are likely to have overlapping validity intervals, either
because they change rarely or the cache contains multiple
versions. Workloads with higher staleness limits experi-
ence more consistency misses (but fewer overall misses)
because they have more stale data that must be matched
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
in-memory DB disk-bound
512 MB 512 MB 64 MB 9 GB
30sstale 15sstale 30sstale 30s stale
Compulsory 33.2% 28.5% 4.3% 63.0%
Stale / Cap. 59.0% 66.1% 95.5% 36.3%
Consistency 7.8% 5.4% 0.2% 0.7%
Figure 8: Breakdown of cache misses by type. Figures
are percentage of total misses.
to other items valid at the same time. The 64 MB-sized
cache’s workload is dominated by capacity misses, be-
cause the cache is smaller than the working set. The
disk-bound experiment sees more compulsory misses be-
cause it has a larger dataset with limited locality, and few
consistency misses because the update rate is slower.
The low fraction of consistency misses suggests that
providing consistency has little performance cost. We
verified this experimentally by modifying our cache to
continue to use our invalidation mechanism, but to read
any data that was valid within the last 30 seconds, blithely
ignoring consistency. The results of this experiment are
shown as the “No consistency” line in Figure 5(a). As
predicted, the benefit it provides over consistency is small.
On the disk-bound configuration, the results could not be
distinguished within experimental error.
9 Related Work
High performance web applications use many different
techniques to improve their throughput. These range from
lightweight application-level caches which typically do
not provide transactional consistency, to database repli-
cation systems that improve database performance while
providing the same consistency guarantees, but do not
address application server load.
9.1 Application-Level Caching
Applying caching at the application layer is an appeal-
ing option because it can improve performance of both
the application servers and the database. Dynamic web
caches operate at the highest layer, storing entire web
pages produced by the application, requiring them to be
regenerated in their entirety when any content changes.
These caches need to invalidate pages when the underly-
ing data changes, typically by requiring the application to
explicitly invalidate pages [37] or specify data dependen-
cies [9, 38]. TxCache obviates this need by integrating
with the database to automatically identify dependencies.
However, full-page caching is becoming less appealing
to application developers as more of the web becomes
personalized and dynamic. Instead, web developers are
increasingly turning to application-level data caches [4,
16, 24, 26, 34] for their flexibility. These caches allow
the application to choose what to store, including query
results, arbitrary application data (such as Java or .NET
USENIX Association
USENIX Association
objects), and fragments of or whole web pages.
These caches present to applications a
GET/PUT/DELETE hash table interface, so the ap-
plication developer must choose keys and correctly
invalidate objects. As we argued in Section 2.1, this
can be a source of unnecessary complexity and software
bugs. Most application object caches have no notion of
transactions, so they cannot ensure even that two accesses
to the cache return consistent values. Some support
transactions within the cache, allowing applications to
atomically update objects in the cache [34, 16], but none
maintain transactional consistency with the database.
9.2 Database Replication
Another popular alternative is to deploy a caching or repli-
cation system within the database layer. These systems
replicate the data tuples that comprise the database, and
allow replicas to perform queries on them. Accordingly,
they can relieve load on the database, but offer no benefit
for application server load.
Some replication systems guarantee transactional con-
sistency by using group communication to execute
queries [12, 19], which can be difficult to scale to large
numbers of replicas [13]. Others offer weaker guarantees
(eventual consistency) [11, 27], which can be difficult to
reason about and use correctly. Still others require the
developer to know the access pattern beforehand [3] or
statically partition the data [8].
Most replication schemes used in practice take a pri-
mary copy approach, where all modifications are pro-
cessed at a master and shipped to slave replicas, usually
asynchronously for performance reasons. Each replica
then maintains a complete, if slightly stale, copy of the
database. Several systems defer update processing to
improve performance for applications that can tolerate
limited amounts of staleness [6, 28, 30]. These protocols
assume that each replica is a single, complete snapshot
of the database, making them infeasible for use in an
application object cache setting where it is not possible to
maintain a copy of every object that could be computed.
In contrast, TxCache’s protocol allows it to ensure con-
sistency even though its cache contains cached objects
that were generated at different times.
Materialized views are a form of in-database caching
that creates a view table containing the result of a query
over one or more base tables, and updating it as the base
tables change. Most work on materialized views seeks to
incrementally update the view rather than recomputing
it in its entirety [15]. This requires placing restrictions
on view definitions, e.g. requiring them to be expressed
in the select-project-join algebra. TxCache’s application-
level functions, in addition to being computed outside
the database, can include arbitrary computation, making
incremental updates infeasible. Instead, it uses invalida-
tions, which are easier for the database to compute [7].
10 Conclusion
Application data caches are an efficient way to scale
database-driven web applications, but they do not inte-
grate well with databases or web applications. They break
the consistency guarantees of the underlying database,
making it impossible for the application to see a consis-
tent view of the entire system. They provide a minimal
interface that requires the application to provide signifi-
cant logic for keeping cached values up to date, and often
requires application developers to understand the entire
system in order to correctly manage the cache.
We provide an alternative with TxCache, an
application-level cache that ensures all data seen by an
application during a transaction is consistent, regardless
of whether it comes from the cache or database. TxCache
guarantees consistency by modifying the database server
to return validity intervals, tagging data in the cache with
these intervals, and then only retrieving values from the
cache that were valid at a single point in time. By using
validity intervals instead of single timestamps, TxCache
can make the best use of cached data by lazily selecting
the timestamp for each transaction.
TxCache provides an easier programming model for
application developers by allowing them to simply des-
ignate cacheable functions, and then have the results of
those functions automatically cached. The TxCache li-
brary handles all of the complexity of managing the cache
and maintaining consistency across the system: it selects
keys, finds data in the cache consistent with the current
transaction, and automatically detects and invalidates po-
tentially changed objects as the database is updated.
Our experiments with the RUBiS benchmark show that
TxCache is effective at improving scalability even when
the application tolerates only a small interval of staleness,
and that providing transactional consistency imposes only
a minor performance penalty.
Acknowledgments
We thank James Cowling, Kevin Grittner, our shepherd
Amin Vahdat, and the anonymous reviewers for their
helpful feedback. This research was supported by NSF
ITR grants CNS-0428107 and CNS-0834239, and by
NDSEG and NSF graduate fellowships.
References
[1] C. Amza, E. Cecchet, A. Chanda, S. Elnikety, A. Cox,
R. Gil, J. Marguerite, K. Rajamani, and W. Zwaenepoel.
Bottleneck characterization of dynamic web site bench-
marks. TRO2-388, Rice University, 2002.
[2] C. Amza, A. Chanda, A. Cox, S. Elnikety, R. Gil, K. Ra-
jamani, W. Zwaenepoel, E. Cecchet, and J. Marguerite.
Specification and implementation of dynamic web site
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI’10) 291
292
[3]
[4]
[5]
[6]
[7]
[8]
[9]
[10]
(11)
[12]
[13]
[14]
(15]
[16]
[17]
[18]
benchmarks. Proc. Workshop on Workload Characteriza-
tion, Nov. 2002.
C. Amza, A. L. Cox, and W. Zwaenepoel. Distributed
versioning: consistent replication for scaling back-end
databases of dynamic content web sites. In Proc. Middle-
ware ’03, Rio de Janeiro, Brazil, June 2003.
R. Bakalova, A. Chow, C. Fricano, P. Jain, N. Kodali,
D. Poirier, S. Sankaran, and D. Shupp. WebSphere dy-
namic cache: Improving J2EE application experience.
IBM Systems Journal, 43(2), 2004.
H. Berenson, P. Bernstein, J. Gray, J. Melton, E. O’ Neil,
and P. O’Neil. A critique of ANSI SQL isolation levels.
In Proc. SIGMOD ’95, San Jose, CA, June 1995.
P. A. Bernstein, A. Fekete, H. Guo, R. Ramakrishnan, and
P. Tamma. Relaxed-currency serializability for middle-tier
caching and replication. In Proc. SIGMOD ’06, Chicago,
IL, 2006.
K. S. Candan, D. Agrawal, W.-S. Li, O. Po, and W.-P.
Hsiung. View invalidation for dynamic content caching in
multitiered architectures. In Proc. VLDB ’02, Hong Kong,
China, 2002.
E. Cecchet, J. Marguerite, and W. Zwaenepoel. C-JDBC:
flexible database clustering middleware. In Proc. USENIX
04, Boston, MA, June 2004.
J. Challenger, A. Iyengar, and P. Dantzig. A scalable
system for consistently caching dynamic web data. In
Proc. INFOCOM ’99, Mar 1999.
J. Cowling, D. R. K. Ports, B. Liskov, R. A. Popa, and
A. Gaikwad. Census: Location-aware membership man-
agement for large-scale distributed systems. In Proc.
USENIX ’09, San Diego, CA, June 2009.
A. Downing, I. Greenberg, and J. Peha. OSCAR: a system
for weak-consistency replication. In Proc. Workshop on
Management of Replicated Data, Nov 1990.
S. Elnikety, W. Zwaenepoel, and F. Pedone. Database
replication using generalized snapshot isolation. In Proc.
SRDS ’05, Washington, DC, 2005.
J. Gray, P. Helland, P. O’ Neil, and D. Shasha. The dangers
of replication and a solution. In Proc. SIGMOD ’96,
Montreal, QC, June 1996.
P. J. Guo and D. Engler. Towards practical incremental
recomputation for scientists: An implementation for the
Python language. In Proc. TAPP ’10, San Jose, CA, Feb.
2010.
A. Gupta, I. S. Mumick, and V. S. Subrahmanian. Main-
taining views incrementally. In Proc. SIGMOD ’93, Wash-
ington, DC, June 1993.
JBoss Cache. http://www. jboss.org/jbosscache/.
D. Karger, E. Lehman, T. Leighton, R. Panigrahy,
M. Levine, and D. Lewin. Consistent hashing and random
trees: distributed caching protocols for relieving hot spots
on the World Wide Web. In Proc. STOC ’97, El Paso, TX,
May 1997.
K. Keeton, C. B. Morrey III, C. A. N. Soules, and
A. Veitch. LazyBase: Freshness vs. performance in infor-
mation management. In Proc. HotStorage ’10, Big Sky,
MT, Oct. 2009.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
[19]
[20]
[21]
[22]
[23]
[24]
[25]
[26]
[27]
[28]
[29]
[30]
[31]
[32]
[33]
[34]
[35]
[36]
[37]
[38]
B. Kemme and G. Alonso. A new approach to developing
and implementing eager database replication protocols.
Transactions on Database Systems, 25(3):333-379, 2000.
L. Lamport. Time, clocks, and ordering of events in a dis-
tributed system. Communications of the ACM, 21(7):558-
565, July 1978.
B. Liskov and R. Rodrigues. Transactional file systems
can be fast. In Proc. ACM SIGOPS European Workshop,
Leuven, Belgium, Sept. 2004.
MediaWiki bugs. http://bugzilla.wikimedia.org/.
Bugs #7474, #7541, #7728, #10463.
MediaWiki bugs. http://bugzilla.wikimedia.org/.
Bugs #8391, #17636.
memcached: a distributed memory object caching system.
http://www.danga.com/memcached.
NCache. http://www.alachisoft.com/ncache/.
OracleAS web cache.
technology/products/ias/web_cache/.
K. Petersen, M. J. Spreitzer, D. B. Terry, M. M. Theimer,
and A. J. Demers. Flexible update propagation for weakly
consistent replication. In Proc. SOSP ’97, Saint Malo,
France, 1997.
C. Plattner and G. Alonso. Ganymed: scalable replication
for transactional web applications. In Proc. Middleware
05, Toronto, Canada, Nov. 2004.
PostgreSQL. http: //www.postgresql.org/.
U. Rohm, K. Bohm, H. Schek, and H. Schuldt. FAS: a
freshness-sensitive coordination middleware for a cluster
of OLAP components. In Proc. VLDB ’02, Hong Kong,
China, 2002.
A. Rowstron and P. Druschel. Pastry: Scalable, decen-
tralized object location and routing for large-scale peer-
to-peer systems. In Proc. Middleware ’01, Heidelberg,
Germany, Nov. 2001.
P. Saab. Scaling memcached at Facebook. http://www.
facebook. com/note.php?note_id=39391378919, Dec.
2008.
A. Salcianu and M. C. Rinard. Purity and side effect
analysis for Java programs. In Proc. VMCAI ’05, Paris,
France, Jan. 2005.
N. Sampathkumar, M. Krishnaprasad, and A. Nori. In-
troduction to caching with Windows Server AppFabric.
Technical report, Microsoft Corporation, Nov 2009.
I. Stoica, R. Morris, D. Liben-Nowell, D. R. Karger, M. F.
Kaashoek, F. Dabek, and H. Balakrishnan. Chord: a scal-
able peer-to-peer lookup protocol for internet applications.
Transactions on Networking, 11(1):149-160, Feb. 2003.
M. Stonebraker. The design of the POSTGRES storage
system. In Proc. VLDB ’87, Brighton, United Kingdom,
Sept. 1987.
H. Yu, L. Breslau, and S. Shenker. A scalable web cache
consistency architecture. SIGCOMM Comput. Commun.
Rev., 29(4):163-174, 1999.
H. Zhu and T. Yang. Class-based cache management for
dynamic web content. In Proc. INFOCOM ’01, 2001.
http://www.oracle.com/
USENIX Association
USENIX Association
Piccolo: Building Fast, Distributed Programs with Partitioned Tables
Russell Power
Jinyang Li
New York University
http://news.cs.nyu.edu/piccolo
Abstract
Piccolo is a new data-centric programming model for
writing parallel in-memory applications in data centers.
Unlike existing data-flow models, Piccolo allows compu-
tation running on different machines to share distributed,
mutable state via a key-value table interface. Piccolo en-
ables efficient application implementations. In particu-
lar, applications can specify locality policies to exploit
the locality of shared state access and Piccolo’s run-time
automatically resolves write-write conflicts using user-
defined accumulation functions.
Using Piccolo, we have implemented applications for
several problem domains, including the PageRank algo-
rithm, k-means clustering and a distributed crawler. Ex-
periments using 100 Amazon EC2 instances and a 12
machine cluster show Piccolo to be faster than existing
data flow models for many problems, while providing
similar fault-tolerance guarantees and a convenient pro-
gramming interface.
1 Introduction
With the increased availability of data centers and cloud
platforms, programmers from different problem domains
face the task of writing parallel applications that run
across many nodes. These application range from ma-
chine learning problems (k-means clustering, neural net-
works training), graph algorithms (PageRank), scientific
computation etc. Many of these applications extensively
access and mutate shared intermediate state stored in
memory.
It is difficult to parallelize in-memory computation
across many machines. As the entire computation is di-
vided among multiple threads running on different ma-
chines, one needs to coordinate these threads and share
intermediate results among them. For example, to com-
pute the PageRank score of web page p, a thread needs
to access the PageRank scores of p’s “neighboring” web
pages, which may reside in the memory of threads run-
ning on different machines. Traditionally, parallel in-
memory applications have been built using message-
passing primitives such as MPI [21]. For many users,
the communication-centric model provided by message-
passing is too low-level an abstraction - they fundamen-
tally care about data and processing data, as opposed to
the location of data and how to get to it.
Data-centric programming models [19, 27, 1], in
which users are presented with a simplified interface
to access data but no explicit communication mecha-
nism, have proven a convenient and popular mecha-
nism for expressing many computations. MapReduce
and Dryad [27] provide a data-flow programming model
that does not expose any globally shared state. While the
data-flow model is ideally suited for bulk-processing of
on-disk data, it is not a natural fit for in-memory compu-
tation: applications have no online access to intermediate
state and often have to emulate shared memory access by
joining multiple data streams. Distributed shared mem-
ory [29, 32, 7, 17] and tuple spaces [13] allow sharing of
distributed in-memory state. However, their simple mem-
ory (or tuple) model makes it difficult for programmers
to optimize for good application performance in a dis-
tributed environment.
This paper presents Piccolo, a data-centric program-
ming model for writing parallel in-memory applications
across many machines. In Piccolo, programmers orga-
nize the computation around a series of application ker-
nel functions, where each kernel is launched as multi-
ple instances concurrently executing on many compute
nodes. Kernel instances share distributed, mutable state
using a set of in-memory tables whose entries reside in
the memory of different compute nodes. Kernel instances
share state exclusively via the key-value table interface
with get and put primitives. The underlying Piccolo run-
time sends messages to read and modify table entries
stored in the memory of remote nodes.
By exposing shared global state, the programming
model of Piccolo offers several attractive features. First,
it allows for natural and efficient implementations for ap-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) = 293
294
plications that require sharing of intermediate state such
as k-means computation, n-body simulation, PageRank
calculation etc. Second, Piccolo enables online applica-
tions that require immediate access to modified shared
state. For example, a distributed crawler can learn of
newly discovered pages quickly as a result of state up-
dates done by ongoing web crawls.
Piccolo borrows ideas from existing data-centric sys-
tems to enable efficient application implementations.
Piccolo enforces atomic operations on individual key-
value pairs and uses user-defined accumulation func-
tions to automatically combine concurrent updates on
the same key (similar to reduce functions in MapRe-
duce [19]). The combination of these two techniques
eliminates the need for fine-grained application-level
synchronization for most applications. Piccolo allows
applications to exploit locality of access to shared state.
Users control how table entries are partitioned across ma-
chines by defining a partitioning function [19]. Based
on users’ locality policies, the underlying run-time can
schedule a kernel instance where its needed table parti-
tions are stored, thereby reducing expensive remote table
access.
We have built a run-time system consisting of one
master (for coordination) and several worker processes
(for storing in-memory table partitions and executing
kernels). The run-time uses a simple work stealing
heuristic to dynamically balance the load of kernel exe-
cution among workers. Piccolo provides a global check-
point/restore mechanism to recover from machine fail-
ures. The run-time uses the Chandy-Lamport snapshot
algorithm [15] to periodically generate a consistent snap-
shots of the execution state without pausing active com-
putations. Upon machine failure, Piccolo recovers by re-
starting the computation from its latest snapshot state.
Experiments have shown that Piccolo is fast and pro-
vides excellent scaling for many applications. The per-
formance of PageRank and k-means on Piccolo is 11x
and 4x faster than that of Hadoop. Computing a PageR-
ank iteration for a | billion-page web graph takes only
70 seconds on 100 EC2 instances. Our distributed web
crawler can easily saturate a 100 Mbps internet uplink
when running on 12 machines.
The rest of the paper is organized as follows. Sec-
tion 2 provides a description of the Piccolo program-
ming model, followed by the design of Piccolo’s run-
time (Section 3). We describe the set of applications we
constructed using Piccolo in Section 4. Section 5 dis-
cusses our prototype implementation. We show Piccolo’s
performance evaluation in Section 6 and present related
work in Section 7.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
2 Programming Model
Piccolo’s programming environment is exposed as a li-
brary to existing languages (our current implementation
supports C++ and Python) and requires no change to un-
derlying OS or compiler. This section describes the pro-
gramming model in terms of how to structure application
programs (§2.1), share intermediate state via key/value
tables (§2.2), optimize for locality of access (§2.3), and
recover from failures(§2.4). We conclude this section by
showing how to implement the PageRank algorithm on
top of Piccolo (§2.5).
2.1 Program structure
Application programs written for Piccolo consist of con-
trol functions which are executed on a single machine,
and kernel functions which are executed concurrently
on many machines. Control functions create shared ta-
bles, launch multiple instances of a kernel function, and
perform global synchronization. Kernel functions consist
of sequential code which read from and write to tables
to share state among concurrently executing kernel in-
stances. By default, control functions execute in a sin-
gle thread and a single thread is created for executing
each kernel instance. However, the programmer is free to
create additional application threads in control or kernel
functions as needed.
Kernel invocation: The programmer uses the Run
function to launch a specified number (mm) of kernel in-
stances executing the desired kernel function on dif-
ferent machines. Each kernel instance has an identifier
0---m—1 which can be retrieved using the my_instance
function.
Kernel synchronization: The programmer invokes a
global barrier from within a control function to wait for
the completion of all previously launched kernels. Cur-
rently, Piccolo does not support pair-wise synchroniza-
tion among concurrent kernel instances. We found that
global barriers are sufficient because Piccolo’s shared ta-
ble interface makes most fine-grained locking operations
unnecessary. This overall application structure, where
control functions launch kernels across one or more
global barriers, is reminiscent of the CUDA model [36]
which also explicitly eschews support for pair-wise
thread synchronization.
2.2 Table interface and semantics
Concurrent kernel instances share intermediate state
across machine through key-value based in-memory ta-
bles. Table entries are spread across all nodes and each
key-value pair resides in the memory of a single node.
Each table is associated with explicit key and value types
which can be arbitrary user-declared serializable types.
As Figure 1 shows, the key-value interface provides a
uniform access model whether the underlying table en-
USENIX Association
USENIX Association
Table<Key, Value>:
clear ()
contains (Key)
get (Key)
put (Key, Value)
# updates the existing entry via
# user-defined accumulation.
update (Key, Value)
# Commit any buffered updates/puts
flush ()
# Return an iterator on a table partition
get_iterator (Partition)
Figure 1: Shared Table Interface
try is stored locally or on another machine. The table
APIs include standard operations such as get, put as
well as Piccolo-specific functions like update, flush,
get_iterator. Only control functions can create tables;
both control and kernel functions can invoke any table
operation.
User-defined accumulation: Multiple kernel in-
stances can issue concurrent updates to the same key.
To resolve such write-write conflict, the programmer can
associate a user-defined accumulation function with each
table. Piccolo executes the accumulator during run-time
to combine concurrent updates on the same key. If the
programmer expects results to be independent from the
ordering of updates, the accumulator must be a commu-
tative and associative function [52].
Piccolo provides a set of standard accumulators such
as summation, multiplication and min/max. To de-
fine an accumulator, the user specifies four functions:
Initialize to initialize an accumulator for a newly cre-
ated key, Accumulate to incorporate the effect of a sin-
gle update operation, Merge to combine the contents of
multiple accumulators on the same key, and View to re-
turn the current accumulator state reflecting all updates
accumulated so far. Accumulator functions have no ac-
cess to global state except for the corresponding table
entry being updated.
User-controlled Table Partitioning: Piccolo uses a
user-specified partition function [19] to divide the key-
space into partitions. Table partitioning is a key primitive
for expressing user programs’ locality preferences. The
programmer specifies the number of partitions (p) when
creating a table. The p partitions of a table are named
with integers 0...p — 1. Kernel functions can scan all en-
tries in a given table partition using the get_iterator
function (see Figure 1).
Piccolo does not reveal to the programmer which node
stores a table partition, but guarantees that all table en-
tries in a given partition are stored on the same machine.
Although the run-time aims to have a load-balanced as-
signment of table partitions to machines, it is the pro-
grammer’s responsibility to ensure that the largest table
partition fits in the available memory of a single machine.
This can usually be achieved by specifying a the number
of partitions to be much larger than the number of ma-
chines.
Table Semantics: All table operations involving a sin-
gle key-value pair are atomic from the application’s per-
spective. Write operations (e.g. update, put) destined
for another machine can be buffered to avoid blocking
kernel execution. In the face of buffered remote writes,
Piccolo provides the following guarantees:
e All operations issued by a single kernel instance on
the same key are applied in their issuing order. Op-
erations issued by different kernel instances on the
same key are applied in some total order [31].
e Upon a successful flush, all buffered writes done
by the caller’s kernel instance will have been com-
mitted to their respective remote locations, and will
be reflected in the response to subsequent gets by
any kernel instance.
e Upon the completion of a global barrier, all ker-
nel instances will have been completed and all their
writes will have been applied.
2.3 Expressing locality preferences
While writes to remote table entries can be buffered at
the local node, the communication latency involved in
fetching remote table entries cannot be effectively hid-
den. Therefore, the key to achieving good application
performance is to minimize remote gets by exploiting
locality of access. By organizing the computation as ker-
nels and shared state as partitioned tables, Piccolo pro-
vides a simple way for programmers to express local-
ity policies. Such policies enable the underlying Piccolo
run-time to execute a kernel instance on a machine that
stores most of its needed data, thus minimizing remote
reads.
Piccolo supports two kinds of locality policies: (1) co-
locate a kernel execution with some table partition, and
(2) co-locate partitions of different tables. When launch-
ing some kernel, the programmer can specify a table ar-
gument in the Run function to express their preference
for co-locating the kernel execution with that table. The
programmer usually launches the same number of ker-
nel instances as the number of partitions in the spec-
ified table. The run-time schedules the i-th kernel in-
stance to execute on the machine that stores the i-th par-
tition of the specified table. To optimize for kernels that
read from more than one table, the programmer uses the
GroupTables (T1,T2, ..) function to co-locate multiple
tables. The run-time assigns the i-th partition of T1,T2....
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) 295
to be stored on the same machine. As a result, by co-
locating kernel execution with one of the tables, the pro-
grammer can avoid remote reads for kernels that read
from the same partition of multiple tables.
2.4 User-assisted checkpoint and restore
Piccolo handles machine failures via a global check-
point/restore mechanism. The mechanism currently im-
plemented is not fully automatic - Piccolo saves a con-
sistent global snapshot of all shared table state, but relies
on users to save additional information to recover the po-
sition of their kernel and control function execution. We
believe this design makes a reasonable trade-off. In prac-
tice, the programming efforts required for checkpoint-
ing user information are relatively small. On the other
hand, our design avoids the overhead and complexities
involved in automatically checkpointing C/C++ executa-
bles.
Based on our experience of writing applications, we
arrived at two checkpointing APIs: one synchronous
(CpBarrier) and one asynchronous (CpPeriodic). Both
functions are invoked from some control function. Syn-
chronous checkpoints are well-suited for iterative appli-
cations (e.g. PageRank) which launch kernels in multiple
rounds separated by global barriers and desire to save
intermediate state every few rounds. On the other hand,
applications with long running kernels (e.g. a distributed
crawler) need to use asynchronous checkpoints to save
their state periodically.
CpBarrier takes as arguments a list of tables and a
dictionary of user data to be saved as part of the check-
point. Typical user data contain the value of some iterator
in the control thread. For example in PageRank, the pro-
grammer would like to record the number of PageRank
iterations computed so far as part of the global check-
point. CpBarrier performs a global barrier and ensures
that the checkpointed state is equivalent to the state of
execution at the barrier.
CpPeriodic takes as arguments a list of tables, a time
interval for periodic checkpointing, and a kernel call-
back function CheckpointCallback. This callback is
invoked for all active kernels on a node immediately after
that node has checkpointed the state for its assigned ta-
ble partitions. The callback function provides a way for
the programmer to save the necessary data required to
restore running kernel instances. Oftentimes this is the
position of an iterator over the partition that is being
processed by a kernel instance. When restoring, Piccolo
reloads the table state on all nodes, and invokes kernel in-
stances with the dictionary saved during the checkpoint.
2.5 Putting it together: PageRank
As a concrete example, we show how to implement
PageRank using Piccolo. The PageRank algorithm [11]
296 9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
tuple PageID(site, page)
const PropagationFactor = 0.85
def PRKernel (Table (PageID,double) curr,
Table (PageID,double) next,
Table (PageID, [PageID]) graph_partition):
for page, outlinks in
graph. get_iterator (my_instance()):
rank = curr[page]
update = PropagationFactor * rank / len(outlinks)
for target in outlinks:
next.update (target, update)
def PageRank (Config conf):
graph = Table (PageID, [PageID]).init ("/dfs/graph")
curr = Table(PageID, double) .init (
graph.numPartitions(),
SumAccumulator, SitePartitioner)
next = Table(PageID, double) .init (
graph.numPartitions (),
SumAccumulator, SitePartitioner)
GroupTables (curr, next, graph)
if conf.restore():
last_iter = curr.restore_from_checkpoint ()
else: last_iter = 0
# run 50 iterations
for i in range(last_iter, 50)
Run (PRKernel,
instances=curr_pr.numPartitions(),
locality=LOC_REQUIRED (curr),
args=(curr, next, graph) )
# checkpoint every 5 iterations, storing the
# current iteration alongside checkpoint data
if is5 ==0:
CpBarrier (tables=curr,
{iteration=i})
else: Barrier ()
# the values accumulated into ’next’ become the
# source values for the next iteration
swap (curr, next)
Figure 2: PageRank Implementation
takes as input a sparse web graph and computes a rank
value for each page. The computation proceeds in mul-
tiple iterations: page i’s rank value in the k-th itera-
tion (p\* ) is the sum of the normalized ranks of its in-
coming neighbors in the previous iteration, i.e. p”) =
(k-1)
Lvjem, Tows[” where Out; denotes page j’s outgoing
neighbors.
The complete PageRank implementation in Piccolo is
shown in Figure 2. The input web graph is represented
as a set of outgoing links, page — target, for each page.
The graph is loaded into the shared in-memory table
(graph) from a distributed file system. For link graphs
too large to fit in memory, Piccolo also supports a read-
only DiskTable interface for streaming data from disk.
The intermediate rank values are kept in two tables:
curr for the ranks to be read in the current iteration,
next for the ranks to be written. The control function
USENIX Association
USENIX Association
Yet
z Put
Pea
table kernel
Partitions
control
thread
Figure 3: The interactions between master and workers in exe-
cuting a Piccolo program.
(PageRank) iteratively launches p PRKernel kernel in-
stances where p is the number of table partitions in
graph (which is identical to that of curr and next). The
kernel instance i scans all pages in the i-th partition of
graph. For each page — target link, the kernel instance
reads the rank value of page in curr, and generates up-
dates for next to increment target’s rank value for the
next iteration.
Since the program generates concurrent updates to
the same key in next, it associates the Sum accumula-
tor with next, which correctly combines updates as de-
sired by the PageRank computation. The overall compu-
tation proceeds in rounds using a global barrier between
PRKerne1 invocations.
To optimize for locality, the program groups tables
graph, curr, next together and expresses preference for
co-locating PRKerne1 executions with the curr table. As
a result, none of the kernel instances need to perform any
remote reads. In addition, the program uses the partition
function, SitePartitioner, to assign the URLs in the
same domain to the same partition. As pages in the same
domain tend to link to one another frequently, such par-
titioning significantly reduces the number of remote up-
dates.
Checkpointing/restoration is straightforward: the con-
trol thread performs a synchronous checkpoint to save
the next table every five iterations and loads the latest
checkpointed table to recover from failure.
3 System Design
This section describes the run-time design for executing
Piccolo programs on a large collection of machines con-
nected via high-speed Ethernet.
3.1 Overview
Piccolo’s execution environment consists of one mas-
ter process and many worker processes, each executing
on a potentially different machine. Figure 3 illustrates
the overall interactions among workers and the master
when executing a Piccolo program. As Figure 3 shows,
the master executes the user control thread by itself and
schedules kernel instances to execute on workers. Addi-
tionally, the master decides how table partitions are as-
signed to workers. Each worker is responsible for storing
assigned table partitions in its memory and handling ta-
ble operations associated with those partitions. Having a
single master does not introduce a performance bottle-
neck: the master informs all workers of the current par-
tition assignment so that workers need not consult the
master to perform performance-critical table operations.
The master begins the execution of a Piccolo pro-
gram by invoking the entry function in the control thread.
Upon each table creation API call, the master decides on
a partition assignment. The master informs all workers
of the partition assignment and each worker initializes
its set of partitions, which are all empty at startup. Upon
each Run API call to execute m kernel instances, the mas-
ter prepares m tasks, one for each kernel instance. The
master schedules these tasks for execution on workers
based on user’s locality preferences. Each worker runs a
single kernel instance at a time and notifies the master
upon task completion. The master instructs each com-
pleted worker to proceed with an additional task if it is
available. Upon encountering a global barrier, the mas-
ter blocks the control thread until all active tasks are fin-
ished.
During kernel execution, a worker buffers update op-
erations destined for remote workers, combines them us-
ing user-defined accumulators and flushes them to re-
mote workers after a short timeout. To handle a get or
put operation, the worker flushes accumulated updates
on the same key before sending the operation to the
remote worker. Each owner applies operations (includ-
ing accumulated updates) in their received order. Piccolo
does not perform caching but supports a limited form
of pre-fetching: after each get_iterator API call, the
worker pre-fetches a portion of table entries beyond the
current iterator value.
Two main challenges arise in the above basic design.
First, how to assign tasks in a load-balanced fashion so as
to reduce the overall wait time on global barriers? This is
particularly important for iterative applications that incur
a global barrier at each iteration of the computation. The
second challenge is to perform efficient checkpointing
and restoration of table state. In the rest of this Section,
we detail how Piccolo addresses both challenges.
3.2 Load-balanced Task Scheduling
Basic scheduling without load-balancing works as fol-
lows. At table creation time, the master assigns table par-
titions to all workers using a simple round-robin assign-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) = 297
298
ment for empty memory tables. For tables loaded from
a distributed file, the master chooses an assignment that
minimizes inter-rack transfer while keeping the number
of partitions roughly balanced among workers. The mas-
ter schedules m tasks according to the specified local-
ity preference, namely, it assigns task i to execute on a
worker storing partition i.
This initial schedule may not be ideal. Due to hetero-
geneous hardware configurations or variable-sized com-
putation inputs, workers can take varying amounts of
time to finish assigned tasks, resulting in load imbalance
and non-optimal use of machines. Therefore, the run-
time needs to load-balance kernel executions beyond the
initial schedule.
Piccolo’s scheduling freedom is limited by two con-
straints: First, no running tasks should be killed. As a
running kernel instance modifies shared table state, re-
executing a terminated kernel instance requires perform-
ing an expensive restore operation from a saved check-
point. Therefore, once a kernel instance is started, it is
better to let the task complete than terminating it halfway
for re-scheduling. By contrast, MapReduce systems do
not have this constraint [28] as reducers do not start ag-
gregation until all mappers are finished. The second con-
straint comes from the need to honor user locality pref-
erences. Specifically, if a kernel instance is to be moved
from one worker to another, its co-located table partitions
must also be transferred across those workers.
Load-balance via work stealing: Piccolo performs
a simple form of load-balancing: the master observes
the progress of different workers and instructs a worker
(Widie) that has finished all its assigned tasks to steal a
not-yet-started task i from the worker (Wpysy) with the
most remaining tasks. We adopt the greedy heuristic of
scheduling larger tasks first. To implement this heuristic,
the master estimates the input size of each task by the
number of keys in its corresponding table partition. The
master collects partition size information from all work-
ers at table loading time as well as at each global barrier.
The master instructs each worker to execute its assigned
tasks in decreasing order of estimated task sizes. Addi-
tionally, the idle worker wijqje always steals the biggest
task among Wpysy’s remaining tasks.
Table partition migration: Because of user local-
ity preference, worker wijgie needs to transfer the corre-
sponding table partition i from wp,sy before it executes
stolen task i. Since table migration occurs while other
active tasks are sending operations to partition i, Piccolo
must take care not to lose, re-order or duplicate opera-
tions from any worker on a given key in order to pre-
serve table semantics. Piccolo uses a multi-phase migra-
tion process that does not require suspending any active
tasks.
The master coordinates the process of migrating parti-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
tion i from w, to wy, which proceeds in two phases. In the
first phase, the master sends message M, to all workers
indicating the new ownership of i. Upon receiving M1,
all workers flush their buffered operations for i to wg and
begin to send subsequent requests for i to w,. Upon the
receipt of M,, wg “pauses” updates to i, and begins to
forward requests received from other workers for i to wp.
Wa then transfers the paused state for i to w,. During this
phase, worker wy, buffers all requests for i received from
Wa or other workers but does not yet handle them.
After the master has received acknowledgments from
all workers that the first phase is complete, it sends M2 to
W, and wy, to complete migration. Upon receiving M2, wa
flushes any pending operations destined for i to w, and
discards the paused state for partition i. w, first handles
buffered operations received from w, in order and then
resumes normal operation on partition i.
As can be seen, the migration process does not block
any update operations and thus incurs little latency over-
head for most kernels. The normal checkpoint/recovery
mechanism is used to cope with faults that might occur
during migration.
3.3 Fault Tolerance
Piccolo relies on user-assisted checkpoint and restore to
cope with both master and worker failures during pro-
gram execution. The Piccolo run-time saves a checkpoint
of program state (including tables and other user-data) on
a distributed file system and restores from the latest com-
pleted checkpoint to recover from a failure.
Checkpoint: Piccolo needs to save a consistent global
checkpoint with low overhead. To ensure consistency,
Piccolo must determine a global snapshot of the program
state. To reduce overhead, the run-time must carry out
checkpointing in the face of actively running kernel in-
stances or the control thread.
We use the Chandy-Lamport (CL) distributed snap-
shot algorithm [15] to perform checkpointing. To save a
CL snapshot, each process records its own state and two
processes incident on a communication channel cooper-
ate to save the channel state. In Piccolo, channel state
can be efficiently captured using only table modification
messages as kernels communicate with each other exclu-
sively via tables.
To begin a checkpoint, the master chooses a new
checkpoint epoch number (£)) and sends the start check-
point message Start, to all workers. Upon receiving the
start message, worker w immediately takes a snapshot
of the current state of its responsible table partitions and
buffers future table operations (in addition to applying
them). Once the table partitions in the snapshot are writ-
ten to stable storage, w sends the marker message Mz,
to all other workers. Worker w then enters a logging
state in which it logs all buffered operations to a replay
USENIX Association
USENIX Association
file. Once w has received markers from all other workers
(Mg w,Vw! #w), it writes the replay log to stable storage
and sends Fing to the master. The master considers the
checkpointing done once it has received Fingy from all
workers.
For asynchronous checkpoints, the master initiates
checkpoints periodically based on a timer. To record
user-data consistently with recorded table state, each
worker atomically takes a snapshot of table state and in-
vokes the checkpoint callback function to save any ad-
ditional user state for its currently running kernel in-
stance. Synchronous checkpoints provide the semantics
that checkpointed state is equivalent to those immedi-
ately after the global barrier. Therefore, for synchronous
checkpointing, each worker waits until it has completed
all its assigned tasks before sending the checkpoint
marker Mz y to all other workers. Furthermore, the mas-
ter saves user-data in the control thread only after it has
received Fing , from all workers. There is a trade-off in
deciding when to start a synchronous checkpoint. If the
master starts the checkpoint too early, e.g. while workers
still have many remaining tasks, replay files become un-
necessarily large. On the other hand, if the master delays
checkpointing until all workers have finished, it misses
opportunities to overlap kernel computation with check-
pointing. Piccolo uses a heuristic to balance this trade-
off: the master begins a synchronous checkpoint as soon
as one of the workers has finished all its assigned tasks.
To simplify the design, the master does not initiate
checkpointing while there is active table migration and
vice-versa.
Restore: Upon detecting any worker failure, the mas-
ter resets the state of all workers and restores compu-
tation from the last completed global checkpoint. Pic-
colo does not checkpoint the internal state of the mas-
ter - if the master is restarted, restoration occurs as nor-
mal, however, the replacement master is free to choose
a different partition assignment and task schedule during
restoration.
4 More Applications
In addition to PageRank, we have implemented four
other applications: a distributed web crawler, k-means,
n-body, matrix multiplication. This section summarizes
how Piccolo’s programming model enables efficient im-
plementation for these applications.
4.1 Distributed Web Crawler
Apart from iterative computations such as PageRank,
Piccolo can be used by applications to distribute and co-
ordinate fine-grained tasks among many machines. To
demonstrate this usage, we implemented a distributed
web crawler. The basic crawler operation is simple: be-
ginning from a few initial URLs, the crawler repeatedly
#local variables kept by each kernel instance
fetch_pool = Queue ()
crawl_output = OutputLog(’ ./crawl.data’ )
def FetcherThread():
while 1:
url = fetch_pool.get ()
txt = download_url (url)
crawl_output.add(url, txt)
for 1 in get_links (txt):
url_table.update(1l, ShouldFetch)
url_table.update (url, Done)
def CrawlKernel (Table (URL,CrawlState) url_table):
for i in range (20)
t = FetcherThread ()
t.start ()
while 1:
for url, status in url_table.my_partition :
if status == ShouldFetch
#omit checking domain in robots table
#omit checking domain in politeness table
url_table.update (url, Fetching)
fetch_pool.add (url)
Figure 4: Snippet of the crawler implementation.
downloads a page and parses it to discover new URLs
to fetch. A practical crawler must also satisfy other im-
portant constraints: (1) honor the robots.txt file of each
web site, (2) refrain from overwhelming a site by cap-
ping fetches to a site at a fixed rate, and (3) avoid repeated
fetches of the same URL.
Our implementation uses three co-located tables:
e The url_table stores the crawling state ToFetch,
Fetching, Blacklisted, Done for each URL. For each
URL p in ToFetch state, the crawler fetches the cor-
responding web page and sets p’s state to Fetching.
After the crawler has finished parsing p and extract-
ing its outgoing links, it sets p’s state to Done.
e The politeness table tracks the last time a page was
downloaded for each site.
e The robots table stores the processed robots file for
each site.
The crawler spawns m kernel instances, one for each
machine. Our implementation is done in Python in order
to utilize Python’s web-related libraries. Figure 4 shows
the simplified crawler kernel (omitting details for pro-
cessing robots.txt and capping per-site download rate).
Each kernel scans its local url_table partitions to find
ToFetch URLs and processes them using a pool of helper
threads. As all three tables are partitioned according to
the SitePartitioner function and co-located with each
other, a kernel instance can efficiently check for the
politeness information and robots entries before down-
loading a URL. Our implementation uses the max ac-
cumulator to resolve write-write conflicts on the same
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) 299
300
URL in url_table according to Done > Blacklisted >
Fetching > ToFetch. This allows the simple and ele-
gant operation shown in Figure 4, where kernels re-
discovering an already-fetched URL p can request up-
dating p’s state to ToFetch and still arrive at the correct
state for p.
Consistent global checkpointing is important for the
crawler’s recovery. Without global checkpointing, the re-
covered crawler may find a page p to be Done but does
not see any of p’s extracted links in the url_table, pos-
sibly causing those URLs to never be crawled. Our im-
plementation performs asynchronous checkpointing ev-
ery 10 minutes so that the crawler loses no more than 10
minutes worth of progress due to node failure. Restoring
from the last checkpoint can result in some pages being
crawled more than once (those lost since the last check-
point), but the checkpoint mechanism guarantees that no
pages will “fall through the cracks.”
4.2 Parallel computation
k-means. The k-means algorithm is an iterative com-
putation for grouping n data points into k clusters in a
multi-dimensional space. Our implementation stores the
assigned centers for data points and the positions of cen-
ters in shared tables. Each kernel instance processes a
subset of data points to compute new center assignments
for those data points and update center positions for the
next iteration using the summation accumulator.
n-body. This application simulates the dynamics of a
set of particles over many discrete time-steps. We im-
plemented an n-body simulation intended for short dis-
tances [43], where particles further than a threshold dis-
tance (r) apart are assumed to have no effect on each
other. During each time-step, a kernel instance processes
a subset of particles: it updates a particle’s velocity and
position based on its current velocity and the positions of
other particles within r distance away. Our implementa-
tion uses a partition function to divide space into cubes
so that a kernel instance mostly performs local reads in
order to retrieve those particles within r distance away.
Matrix multiplication. Computing C = AB where A
and B are two large matrices is a common primitive in
numerical linear algebra. The input and output matri-
ces are divided into m x m blocks stored in three tables.
Our implementation co-locates tables A,B,C. Each ker-
nel instance processes a partition of table C by computing
Cij = Le Aik Br,j-
5 Implementation
Piccolo has been implemented in C++. We provide both
C++ and Python APIs so that users can write kernel
and control functions in either C++ or Python. We use
SWIG [6] for constructing a Python interface to Pic-
colo. Our implementation re-uses a number of existing
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
libraries, such as OpenMPI for communication, Google’s
protocol buffers for object serialization, and LZO for
compressing on-disk tables.
All the parallel computations (PageRank, k-means, n-
body and matrix multiplication) are implemented using
the C++ Piccolo API. The distributed crawler is imple-
mented using the Python API.
6 Evaluation
We tested the performance of Piccolo on the applica-
tions described in Section 4. Some applications, such as
PageRank and k-means, can also be implemented using
the existing data-flow model and we compared the per-
formance of Piccolo with that of Hadoop for these appli-
cations.
The highlights of our results are:
e Piccolo is fast. PageRank and k-means are 11 x and
4x faster than those on Hadoop. When compared
against the results published for DryadLing [53], in
which a PageRank iteration on a 900M page graph
were performed in 69 seconds, Piccolo finishes an
iteration for a 1B page graph in 70 seconds on EC2,
while using 1/5 the number of CPU cores.
e Piccolo scales well. For all applications evaluated,
increasing the number of workers shows a nearly
linear reduction in the computation time. Our 100-
instance EC2 experiment on PageRank also demon-
strates good scaling.
e Piccolo can help a non-conventional application like
the crawler to achieve good parallel performance.
Our crawler, despite being implemented in Python,
manages to saturate the Internet bandwidth of our
cluster.
6.1 Test Setup
Most experiments were performed using our local clus-
ter of 12 machines: 6 of the machines have | quad-
core Intel Xeon X3360 (2.83GHz) processor with 4GB
memory, the other 6 machines have 2 quad-core Xeon
E5520 (2.27GHz) processors with 8GB memory. All
machines are connected via a commodity gigabit ether-
net switch. Our EC2 experiments involve 100 “large in-
stances” each with 7.5GB memory and 2 “virtual cores”
where each virtual core is equivalent to a 2007-era single
core 2.5GHz Intel Xeon processor. In all experiments,
we created one worker process per core and pinned each
worker to use that core.
For scaling experiments, we vary the input size of dif-
ferent applications. Table 5 shows the default and max-
imum input size used for each application. We generate
the web link graph for PageRank based on the statistics
of a web graph of 100M pages in UK[9]. Specifically, we
USENIX Association
USENIX Association
Application Default input size Maximum input size
PageRank 100M pages 1B pages
k-means 25M points, 100 clusters 1B points, 100 clusters
n-body 100K points 10M points
Matrix Multiply edge size = 2500 edge size = 6000
Figure 5: Application input sizes
84
a 6]
>
so
o
a fA K-Means
4 a i N-Bod
eer] Matrix Multiply
K IN PageRank
0 Al ---Ideal
8 16 32 64
Workers
Figure 6: Scaling performance (fixed default input size)
extract the distributions for the number of pages in each
site and the ratio of intra/inter-site links. We generate a
web graph of any size by sampling from the site size dis-
tribution until the desired number of pages is reached;
outgoing links are then generated for each page in a site
based on the distribution of the ratio of intra/inter-site
links. For other applications, we use randomly generated
inputs.
6.2 Scaling Performance
Figure 6 shows application speedup as the number of
workers (NV) increases from 8 to 64 for the default input
size. All applications are CPU-bound and exhibit good
speedup with increasing N. Ideally, all applications (ex-
cept for PageRank) have perfectly balanced table par-
titions and should achieve linear speedup. However, to
have reasonable running time at N=8, we choose a rela-
tively small default input size. Thus, as N increases to
64, Piccolo’s overhead is no longer negligible relative
to applications’ own computation (e.g. k-means finishes
each iteration in 1.4 seconds at N=64), resulting in 20%
less than ideal speedup. PageRank’s table partitions are
not balanced and work stealing becomes important for its
scaling (see § 6.5).
We also evaluate how applications scale with increas-
ing input size by adjusting input size to keep the amount
of computation per worker fixed with increasing N. We
scale the input size linearly with N for PageRank and k-
means. For matrix multiplication, the edge size increases
as O(N!/3), We do not show results for n-body because it
is difficult to scale input size to ensure a fixed amount of
computation per worker. For these experiments, the ideal
scaling has constant running time as input size increases
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
Relative Runtime
oO
o
0.45
lM K-Means
0.27 Gi Matrix Multiply
fA PageRank
0 | --- Ideal
8 16 32 64
Workers
Figure 7: Scaling input size.
1.07 pp eee a [a]
me
= 0.87
Run
o 0.6-]
tiv
09.45
Rel
Hi K-Means
Pagerank
---Ideal
0.27
0.0
12 24 48
Workers
100 200
Figure 8: Scaling input size on EC2.
with N. As Figure 7 shows, the achieved scaling for all
applications is within 20% of the ideal number.
6.3 EC2
We investigated how Piccolo scales with a larger number
of machines using 100 EC2 instances. Figure 8 shows
the scaling of PageRank and k-means on EC2 as we in-
crease their input size with N. We were somewhat sur-
prised to see that the resulting scaling on EC2 is bet-
ter than achieved on our small local testbed. Our local
testbed’s CPU performance exhibited quite some vari-
ability, impacting scaling. After further investigation, we
believe the source for such variability is likely due to dy-
namic CPU frequency scaling.
At N=200, PageRank finishes in 70 seconds for a 1B
page link graph. On a similar sized graph (900M pages),
our local testbed achieves comparable performance ( 80
seconds) with many fewer workers (N=64), due to the
higher performing cores on our local testbed.
6.4 Comparison with Other Frameworks
Comparison with Hadoop: We implemented PageRank
and k-means in Hadoop to compare their performance
against that of Piccolo. The rest of our applications, in-
cluding the distributed web crawler, n-body and matrix
301
302
multiplication, do not have any straightforward imple-
mentation with Hadoop’s data-flow model.
For the Hadoop implementation of PageRank, as with
Piccolo, we partition the input link graph by site. Dur-
ing execution, each map task has locality with the parti-
tion of graph it is operating on. Mappers join the graph
and PageRank score inputs, and use a combiner to aggre-
gate partial results. Our Hadoop k-means implementation
is highly optimized. Each mapper fetches all 100 cen-
troids from the previous iteration via Hadoop File Sys-
tem (HDFS), computes the cluster assignment of each
point in its input stream, and uses a local hash map to ag-
gregate the updates for each cluster. As a result, a reducer
only needs to aggregate one update from each mapper to
generate the new centroid.
We made extensive efforts to optimize the perfor-
mance of PageRank and k-means on Hadoop including
changes to Hadoop itself. Our optimizations include us-
ing raw memory comparisons, using primitive types to
avoid Java’s boxing and unboxing overhead, disabling
checksumming, improving Hadoop’s join implementa-
tion etc. Figure 9 shows the running time of Piccolo
and Hadoop using the default input size. Piccolo signif-
icantly outperforms Hadoop on both benchmarks (11x
for PageRank and 4x for k-means with N=64). The
performance difference between Hadoop and Piccolo is
smaller for k-means because of our optimized k-means
implementation; the structure of PageRank does not ad-
mit a similar optimization.
Although we expected to see some performance dif-
ference because Hadoop is implemented in Java while
Piccolo in C++, the order of magnitude difference came
as a surprise. We profiled the PageRank implementation
on Hadoop to find the contributing factors. The leading
causes for the slowdown are: (1) sorting keys in the map
phase (2) serializing and de-serializing data streams and
(3) reading and writing to HDFS. Key sorting alone ac-
counted for nearly 50% of the runtime in the PageR-
ank benchmark, and serialization another 15%. In con-
trast, with Piccolo, the need for (1) is eliminated and
the overhead associated with (2) and (3) is greatly re-
duced. PageRank rank values are stored in memory and
are available across iterations without being serialized to
a distributed file system. In addition, as most outgoing
links point to other pages at the same site, a kernel in-
stance ends up performing most updates directly to lo-
cally stored table data, thereby avoiding serialization for
those updates entirely.
Comparison with MPI: We compared the the perfor-
mance of matrix multiplication using Piccolo to a third-
party MPI-based implementation [2]. The MPI version
uses Cannon’s algorithm for blocked matrix multiplica-
tion and uses MPI specific communication primitives to
handle data broadcast and the simultaneous sending and
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
=
® 10004
a
> 1007]
=]
® 1074
E
~< 1
g
8 1000-74
2
< 1007
cc
o ae i Al a a Piccolo
a 1 Hadoop
8 16 32 64
Workers
Figure 9: Per-iteration running time of PageRank and k-means
in Hadoop and Piccolo (fixed default input size).
oO
a”
ke
oO
2
s i Piccol
oO Iccolo
ns 0 MPI
16 25 36 64
Workers
Figure 10: Runtime of matrix multiply, scaled relative to MPI.
receiving of data. For Piccolo, we implemented the naive
blocked multiplication algorithm, using our distributed
tables to handle the communication of matrix state. As
Piccolo relies on MPI primitives for communication, we
do not expect to see performance advantage, but are
more interested in quantifying the amount of overhead
incurred.
Figure 10 shows that the running time of the Piccolo
implementation is no more than 10% of the MPI imple-
mentation. We were surprised to see that our Piccolo im-
plementation out-performed the MPI version in exper-
iments with more workers. Upon inspection, we found
that this was due to slight performance differences be-
tween machines in our cluster; as the MPI implementa-
tion has many more synchronization points than that of
Piccolo, it is forced to wait for slower nodes to catch up.
6.5 Work Stealing and Slow Machines
The PageRank benchmark provides a good basis for test-
ing the effect of work stealing because the web graph par-
titions have highly variable sizes: the largest partition for
the 900M-page graph is 5 times the size of the smallest.
Using the same benchmark, we also tested how perfor-
mance changed when one worker was operating slower
then the rest. To do so, we ran a CPU-intensive program
on one core that resulted in the worker bound to that core
USENIX Association
USENIX Association
12077
Runtime (secs
a
2
Normal - No Stealing
Normal - Stealing
Slow Worker - No Stealing
i Slow Worker - Stealing
RSSSASSAASASAAAANAAAAAASSSSSS
RSS AMARA
/
y
Z
°
|
S
\
0 1 2 3 4
Iteration number
Figure 11: Effect of Work Stealing and Slow Workers
having only 50% of the CPU time of the other workers.
The results of these tests are shown in Figure 11. Work
stealing improves running time by 10% when all ma-
chines are operating normally. The improvement is due
to the imbalance in the input partition sizes - when run
without work stealing, the computation waits longer for
the workers processing more data to catch up.
The effect of slow workers on the computation is more
dramatic. With work-stealing disabled, the runtime is
nearly double that of the normal computation, as each
iteration must wait for the slowest worker to complete
all assigned tasks. Enabling work stealing improves the
situation dramatically - the computation time is reduced
to less then 5% over that of the non-slow case.
6.6 Checkpointing
We evaluated the checkpointing overhead using the
PageRank, k-means and n-body problems. Compared to
the other problems, PageRank has a larger table that
needs to be checkpointed, making it a more demand-
ing test of checkpoint/restore performance. In our ex-
periment, each worker wrote its checkpointed table par-
titions to the local disk. Figure 12 shows the runtime
when checkpointing is enabled relative to when there
is no checkpointing. For the naive synchronous check-
pointing strategy, the master starts checkpointing only
after all workers have finished. For the optimized strat-
egy, the master initiates the checkpoint as soon as one of
the workers has finished. As the figure shows, overhead
of the optimized checkpointing strategy is quite negligi-
ble (~2%) and the optimization of starting checkpointing
early results in significant reduction of overhead for the
larger PageRank checkpoint.
Limitations of global checkpoint and restore: The
global nature of Piccolo’s failure recovery mechanism
raises the question of scalability. As the of a cluster in-
creases, failure becomes more frequent; this causes more
frequent checkpointing and restoration which consume a
larger fraction of the overall computation time. While we
lacked the machine resources to directly test the perfor-
mance of Piccolo on thousands of machines, we estimate
scalability limit of Piccolo’s checkpointing mechanism
op
Relative Time
bit
i Pagerank
N-Body
0.8 K-Means
Naive Optimized
Checkpoint Strategy
Figure 12: Checkpoint overhead. Per-iteration runtime is scaled
relative to without checkpointing.
6 MTBF
© 10qrs2n2
> | <3 YOarSmn me me ee
E oe 1 year
806
g 0.47 3 weeks
S 0.27
3 OF | | | |
a 0 2000 4000 6000 8000
Machines
Figure 13: Expected scaling for large clusters.
based on expected machine uptime.
We consider a hypothetical cluster of machines with
16GB of RAM and 4 disk drives. We measured the time
taken to checkpoint and restore such a machine in the
“worst case” - a computation whose table state uses all
available system memory. We estimate the fraction of
time a Piccolo computation would spend working pro-
ductively (not in a checkpoint or restore state), for vary-
ing numbers of machines and failure rates. In our model,
we assume that machine failures arrive at a constant in-
terval defined by the failure rate and the number of ma-
chines in a cluster. While this is a simplification of real-
life failure behavior, it is a worst-case scenario for the
restore mechanism, and as such provides a useful lower
bound. The expected efficiency based on our model is
shown in Figure 13. For well maintained data-centers
that we are familiar with, the average machine uptime is
typically around | year. For these data-centers, the global
checkpointing mechanism can efficiently scale up to a
few thousand machines.
6.7 Distributed Crawler
We evaluated our distributed crawler implementation us-
ing various numbers of workers. The URL table was ini-
tialized with a seed set of 1000 URLs. At the end of a 30
minutes run of the experiment, we measured the num-
ber of pages crawled and bytes downloaded. Figure 14
shows the crawler’s web page download throughput in
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) — 303
304
MBytes/s
°
0 |
0 10 20 30 40 50 60 70
Workers
Figure 14: Crawler throughput
MBytes/sec as N increases from 1 to 64. The crawler
spends most CPU time in the Python code for pars-
ing HTML and URLs. Therefore, its throughput scales
approximately linearly with N. At N=32, the crawler
download throughput peaks at ~ L|OMB/s which is limited
by our 100-Mbps Internet uplink. There are highly op-
timized single-server crawler implementations that can
sustain higher download rates than 100Mbps [49]. How-
ever, our Piccolo-based crawler could potentially scale
to even higher download rates despite being built using
Python.
7 Related Work
Communication-oriented models: Communication-
based primitives such as MPI [21] and Parallel Virtual
Machine (PVM [46]) have been popular for construct-
ing distributed programs for many years. MPI and PVM
offer extensive messaging mechanisms including unicast
and broadcast as well as support for creating and manag-
ing remote processes in a distributed environment. There
has been continuous research on developing experimen-
tal features for MPI, such as optimization of collective
operations [3], fault-tolerance via machine virtualiza-
tion [34] and the use of hybrid checkpoint and logging
for recovery [10]. MPI has been used to build very high
performance applications - its support of explicit com-
munication allows considerable flexibility in writing ap-
plications to take advantage of a wide variety of network
topologies in supercomputing environments. This flexi-
bility has a cost in the form of complexity - users must
explicitly manage communication and synchronization
of state between workers, which can become difficult to
do while attempting to retain efficient and correct execu-
tion.
BSP (Bulk Synchronous Parallel) is a high-level
communication-oriented model [50]. In this model,
threads execute on different processors with local mem-
ory, communicate with each other using messages, and
perform global-barrier synchronization. BSP implemen-
tations are typically realized using MPI [25]. Recently,
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
the BSP model has been adopted in the Pregel framework
for parallelizing work on large graphs [33].
Distributed shared-memory: The complexity of pro-
gramming for communication-oriented models drove a
wave of research in the area of distributed shared mem-
ory (DSM) systems [30, 29, 32, 7]. Most DSM systems
aim to provide transparent memory access, which causes
programs written for DSMs to incur many fine-grained
synchronization events and remote memory reads. While
initially promising, DSM research has fallen off as the
ratio of network latency to local CPU performance has
widened, making naive remote accesses and synchro-
nization prohibitively expensive.
Parallel Global Address Space (PGAS) [17, 35, 51]
are a set of language extensions to realize a distributed
shared address space. These extensions try to ameliorate
the latency problems of DSM by allowing users to ex-
press affinities of portions of shared memory with a par-
ticular thread, thereby reducing the frequency of remote
memory references. They retain the low level (flat mem-
ory) interface common to DSM. As a result, applica-
tions written for PGAS systems still require fine-grained
synchronization when operating on non-primitive data-
types, or in order to aggregate several values (for in-
stance, computing the sum of a memory location with
multiple writers).
Tuple spaces, as seen in coordination languages such
as Linda [13] and more recently JavaSpaces [22], expose
to users a global tuple-space accessible from all partic-
ipating threads. Although tuple spaces provide atomic
primitives for reading and writing tuples, they are not in-
tended for high-frequency access. As such, there is no
support for locality optimization nor write-write conflict
resolution.
MapReduce and Dataflow models: In recent years,
MapReduce has emerged as a popular programming
model for parallel data processing [19]. There are many
recent efforts inspired by MapReduce ranging from gen-
eralizing MapReduce to support the join operation [27],
improving MapReduce’s pipelining performance [16],
building high-level languages on top of MapReduce (e.g.
DryadLINQ [53], Hive [48], Pig [37] and Sawzall [40]).
FlumeJava [14] provides a set of collection abstractions
and parallel execution primitives which are optimized
and compiled down to a sequence of MapReduce opera-
tions.
The programming models of MapReduce [19] and
Dryad [27] are instances of stream processing, or
data-flow models. Because of MapReduce’s popularity,
programmers start using it to build in-memory itera-
tive applications such as PageRank, even though the
data-flow model is not a natural fit for these appli-
cations. Spark [54] proposes to add distributed read-
only in-memory cache to improve the performance of
USENIX Association
USENIX Association
MapReduce-based iterative computations.
Single-machine shared memory models: Many pro-
gramming models are available for parallelizing exe-
cution on a single machine. In this setting, there ex-
ists a physically-shared memory among computing cores
supporting low-latency memory access and fast syn-
chronization between threads of computation, which are
not available in a distributed environment. Although
there are also popular streaming/data-flow models [44,
47, 12], most parallel models for a single machine
are based on shared-memory. For the GPU platform,
there are CUDA [36] and OpenCL [24]. For multi-
core CPUs, Cilk [8] and more recently, Intel’s Thread
Building Blocks [41] provide support for low-overhead
thread creation and dispatching of tasks at a fine level.
OpenMP [18] is a popular shared-memory model among
the scientific computing community: it allows users
to target sections of code for parallel execution and
provides synchronization and reduction primitives. Re-
cently, there have been efforts to support OpenMP pro-
grams across a cluster of machines [26, 5]. However,
based on software distributed shared memory, the result-
ing implementations suffer from the same limitations of
DSMs and PGAS systems.
Distributed data structures: The goal of distributed
data structures is to provide a flexible and scalable data
storage or caching interface. Examples of these include
DDS [23], Memcached [39], the recently proposed Ram-
Cloud [38], and many key-value stores based on dis-
tributed hash tables [4, 20, 45, 42]. These systems do
not seek to provide a computation model, but rather are
targeted towards loosely-coupled distributed applications
such as web serving.
8 Conclusion
Parallel in-memory application need to access and share
intermediate state that reside on different machines.
Piccolo provides a programming model that supports
the sharing of mutable, distributed in-memory state via
a key/value table interface. Piccolo helps applications
achieve high performance by optimizing for locality of
access to shared state and having the run-time auto-
matically resolve write-write conflicts using application-
specified accumulation functions.
Acknowledgments
Yasemin Avcular and Christopher Mitchell ran some of
the Hadoop experiments. We thank the many people who
have improved this work through discussion and reviews:
the members of NeWS group at NYU, Frank Dabek,
Rob Fergus, Michael Freedman, Robert Grimm, Wil-
son Hsieh, Frans Kaashoek, Jinyuan Li, Robert Morris,
Sam Roweis, Torsten Suel, Junfeng Yang, Nickolai Zel-
dovich.
References
[1] Apache hadoop. http: //hadoop. apache. org.
[2] Example matrix multiplication implementation using mpi. http:
//www.cs.umanitoba.ca/~comp4510/examples.html.
[3] ALMAsI, G., HEIDELBERGER, P., ARCHER, C. J., MAR-
TORELL, X., ERWAY, C. C., MOREIRA, J. E., STEINMACHER-
BUROW, B., AND ZHENG, Y. Optimization of MPI collective
communication on BlueGene/L systems. In Proceedings of the
19th annual international conference on Supercomputing (New
York, NY, USA, 2005), ICS ’05, ACM, pp. 253-262.
[4] ANDERSEN, D. G., FRANKLIN, J., KAMINSKY, M., PHAN-
ISHAYEE, A., TAN, L., AND VASUDEVAN, V. FAWN: a fast
array of wimpy nodes. In SOSP (2009), J. N. Matthews and T. E.
Anderson, Eds., ACM, pp. 1-14.
[5] BASUMALLIK, A., MIN, S.-J., AND EIGENMANN, R. Program-
ming distributed memory sytems using OpenMP. Parallel and
Distributed Processing Symposium, International 0 (2007), 207.
[6] BEAZLEY, D. M. Automated scientific software scripting with
SWIG. Future Gener. Comput. Syst. 19 (July 2003), 599-609.
[7] BERSHAD, B. N., ZEKAUSKAS, M. J., AND SAWDON, W. The
Midway Distributed Shared Memory System. In Proceedings
of the 38th IEEE Computer Society International Conference
(1993).
[8] BLUMOFE, R. D., JOERG, C. F., KUSZMAUL, B. C., LEISER-
SON, C. E., RANDALL, K. H., AND ZHOU, Y. Cilk: an effi-
cient multithreaded runtime system. In PPOPP ’95: Proceedings
of the fifth ACM SIGPLAN symposium on Principles and prac-
tice of parallel programming (New York, NY, USA, 1995), ACM,
pp. 207-216.
[9] BOLDI, P., AND VIGNA, S. The WebGraph framework I: Com-
pression techniques. In Proc. of the Thirteenth International
World Wide Web Conference (WWW 2004) (Manhattan, USA,
2004), ACM Press, pp. 595-601.
[10] BOSILCA, G., BOUTEILLER, A., CAPPELLO, F., DJILALI, S.,
FEDAK, G., GERMAIN, C., HERAULT, T., LEMARINIER, P.,
LODYGENSKY, O., MAGNIETTE, F., NERI, V., AND SELIKHOV,
A. Mpich-v: toward a scalable fault tolerant mpi for volatile
nodes. In Proceedings of the 2002 ACM/IEEE conference on Su-
percomputing (Los Alamitos, CA, USA, 2002), Supercomputing
°02, IEEE Computer Society Press, pp. 1-18.
[11] BRIN, S., AND PAGE, L. The anatomy of a large-scale hypertex-
tual web search engine. Computer Networks and ISDN Systems
30, 1-7 (1998), 107 — 117. Proceedings of the Seventh Interna-
tional World Wide Web Conference.
[12] Buck, I., FOLEY, T., HORN, D., SUGERMAN, J., FATA-
HALIAN, K., HOUSTON, M., AND HANRAHAN, P. Brook for
GPUs: stream computing on graphics hardware. In ACM SIG-
GRAPH 2004 Papers (2004), ACM, p. 786.
[13] CARRIERO, N., AND GELERNTER, D. Linda in context. Com-
mun. ACM 32, 4 (1989), 444-458.
[14] CHAMBERS, C., RANIWALA, A., PERRY, F., ADAMS, S.,
HENRY, R. R., BRADSHAW, R., AND WEIZENBAUM, N.
Flumejava: Easy, efficient data-parallel pipelines. In PLDI - ACM
SIGPLAN 2010 (2010).
[15] CHANDY, K. M., AND LAMPORT, L. Distributed snapshots: de-
termining global states of distributed systems. ACM Transactions
on Computer Systems (TOCS) 3 (1985), 63-75.
[16] CONDIE, T., CONWAY, N., ALVARO, P., AND HELLERSTEIN, J.
MapReduce online. In NSDIJ (2010).
[17] CONSORTIUM, U. UPC language specifications, v1.2. Tech. rep.,
Lawrence Berkeley National Lab, 2005.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) 305
306
[18]
[19]
[20]
[21]
[22]
[23]
[24]
[25]
[26]
[27]
[28]
[29]
[30]
[31]
[32]
[33]
[34]
[35]
[36]
DAGUM, L., AND MENON, R. Open MP: An Industry-Standard
API for Shared-Memory Programming. IEEE Computational
Science and Engineering 5, 1 (1998), 46-55.
DEAN, J., AND GHEMAWAT, S. Mapreduce: Simplified data pro-
cessing on large clusters. In Symposium on Operating System
Design and Implementation (OSDI) (2004).
DECANDIA, G., HASTORUN, D., JAMPANI, M., KAKULAPATI,
G., LAKSHMAN, A., PILCHIN, A., SIVASUBRAMANIAN, S.,
VOSSHALL, P., AND VOGELS, W. Dynamo: Amazon’s highly
available key-value store. In ACM Symposium on Operating Sys-
tems Principles (Oct. 2007), pp. 205-220.
FORUM, M. MPI 2.0 standard, 1997.
FREEMAN, E., ARNOLD, K., AND HUPFER, S. JavaSpaces
Principles, Patterns, and Practice. Addison-Wesley Longman
Ltd., Essex, UK, UK, 1999.
GRIBBLE, S. D., BREWER, E. A., HELLERSTEIN, J. M., AND
CULLER, D. Scalable, distributed data structures for internet ser-
vice construction. In OSDI’00: Proceedings of the 4th conference
on Symposium on Operating System Design & Implementation
(Berkeley, CA, USA, 2000), USENIX Association, pp. 22-22.
Group, K. O. W. The OpenCL specification. Tech. rep., 2009.
HILL, J.,. MCCOLL, W., STEFANESCU, D., GOUDREAU, M.,
LANG, K., RAO, S., SUEL, T., TSANTILAS, T., AND BISSEL-
ING, H. Bsplib: The bsp programming library. Parallel Comput-
ing 24 (1998).
HOEFLINGER, J. P. Extending OpenMP to clusters. Tech. rep.,
Intel, 2009.
ISARD, M., BUDIU, M., YU, Y., BIRRELL, A., AND FETTERLY,
D. Dryad: Distributed data-parallel programs from sequential
building blocks. In European Conference on Computer Systems
(EuroSys) (2007).
ISARD, M., PRABHAKARAN, V., CURREY, J., WIEDER, U.,
TALWAR, K., AND GOLDBERG, A. Quincy: Fair scheduling for
distributed computing clusters. In SOSP (2010).
JOHNSON, K. L., KAASHOEK, M. F., AND WALLACH, D. A.
CRL: High-performance all-software distributed shared memory.
In SOSP (1995).
KELEHER, P., Cox, A. L., AND ZWAENEPOEL, W. Lazy release
consistency for software distributed shared memory. In Jn Pro-
ceedings of the 19th Annual International Symposium on Com-
puter Architecture (1992).
LAMPORT, L. How to make a multiprocessor that correctly exe-
cutes multiprocess programs. [EEE Transactions on Computers
28, 9 (1979).
LI, K., AND HUDAK, P. Memory coherence in shared vir-
tual memory systems. ACM Transactions on Computer Systems
(TOCS) 7 (1989), 321-359.
MALEWICZ, G., AUSTERN, M. H., BIK, A. J., DEHNERT,
J. C., HORN, I., LEISER, N., AND CZAJKOWSKI, G. Pregel:
a system for large-scale graph processing. In SIGMOD ’10: Pro-
ceedings of the 2010 international conference on Management of
data (New York, NY, USA, 2010), ACM, pp. 135-146.
NAGARAJAN, A. B., MUELLER, F., ENGELMANN, C., AND
Scott, S. L. Proactive fault tolerance for hpc with xen virtu-
alization. In Proceedings of the 21st annual international confer-
ence on Supercomputing (New York, NY, USA, 2007), ICS ’07,
ACM, pp. 23-32.
NUMRICH, R. W., AND REID, J. Co-array Fortran for parallel
programming. SIGPLAN Fortran Forum 17 (August 1998), 1-31.
NVIDIA. CUDA programming guide (ver 3.0).
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
[37]
[38]
[39]
[40]
[41]
[42]
[43]
[44]
[45]
[46]
[47]
[48]
[49]
[50]
[51]
[52]
[53]
[54]
OLSON, C., REED, B., SRIVASTAVA, U., KUMAR, R., AND
TOMKINS, A. Pig Latin: A not-so-foreign language for data pro-
cessing. In ACM SIGMOD (2008).
OUSTERHOUT, J., AGRAWAL, P., ERICKSON, D., KOZYRAKIS,
C., LEVERICH, J., MAZIERES, D., MITRA, S., NARAYANAN,
A., PARULKAR, G., ROSENBLUM, M., RUMBERL, S., STRAT-
MANN, E., AND STUTSMAN, R. The case for RAMclouds: Scal-
able high-performance storage entirely in DRAM. In Operating
system review (Dec. 2009).
PHILLIPS, L., AND FITZPATRICK, B. Livejournal’s backend and
memcached: Past, present, and future. In LISA (2004), USENIX.
PIKE, R., DORWARD, S., GRIESEMER, R., AND QUINLAN, S.
Interpreting the data: Parallel analysis with Sawzall. In Scientific
Programming (2005).
REINDERS, J. Intel threading building blocks: outfitting C++ for
multi-core processor parallelism. O’ Reilly Media, Inc., 2007.
ROWSTRON, A., AND DRUSCHEL, P. Pastry: Scalable, dis-
tributed object location and routing for large-scale peer-to-peer
systems. In /8th IFIP/ACM International Conference on Dis-
tributed Systems Platforms (Nov. 2001).
SINGH, J. P., WEBER, W.-D., AND GUPTA, A. SPLASH: Stan-
ford parallel applications for shared-memory. Tech. rep., Stanford
University, 1991.
STEPHENS, R. A survey of stream processing, 1995.
STOICA, I., MORRIS, R., LIBEN-NOWELL, D., KARGER,
D. R., KAASHOEK, M. F., DABEK, F., AND BALAKRISHNAN,
H. Chord: A scalable peer-to-peer lookup protocol for Internet
applications. IEEE/ACM Transactions on Networking (2002),
149-160.
SUNDERAM, V. PVM: A framework for parallel distributed com-
puting. Concurrency: Practice and Experience (1990), 315-339.
THIES, W., KARCZMAREK, M., AND AMARASINGHE, S.
Streamlt: A language for streaming applications. In Compiler
Construction (2002), Springer, pp. 49-84.
THUSOO, A., SARMA, J. S., JAIN, N., SHAO, Z., CHAKKA, P.,
ANTHONY, S., LIU, H., WYCKOFF, P., AND MURTHY, R. Hive:
a warehousing solution over a map-reduce framework. Proc.
VLDB Endow. 2 (August 2009), 1626-1629.
TSANG LEE, H., LEONARD, D., WANG, X., AND LOGUINOV,
D. Irlbot: Scaling to 6 billion pages and beyond. In WWW Con-
ference (2008).
VALIANT, L. A bridging model for parallel computation. Com-
munications of the ACM 33 (1990).
YELICK, K., SEMENZATO, L., PIKE, G., MIYAMOTO, C., LI-
BLIT, B., KRISHNAMURTHY, A., GRAHAM, P. H. S., GAY, D.,
COLELLA, P., AND AIKEN, A. Titanium: A high-performance
Java dialect. Concurrency: Practice and Experience 10, 11
(1998).
Yu, Y., GUNDA, P. K., AND ISARD, M. Distributed aggrega-
tion for data-parallel computing: Interfaces and implementations.
In ACM Symposium on Operating Systems Principles (SOSP)
(2009).
Yu, Y., ISARD, M., FETTERLY, D., BUDIU, M., ERLINGSSON,
U., GUNDA, P. K., AND CURREY, J. DryadLINQ: A system for
general-purpose distributed data-parallel computing using a high-
level language. In Symposium on Operating System Design and
Implementation (OSDI) (2008).
ZAHARIA, M., CHOWDHURY, N. M. M., FRANKLIN, M.,
SHENKER, S., AND STOICA, I. Spark: Cluster Computing with
Working Sets. Tech. Rep. UCB/EECS-2010-53, EECS Depart-
ment, University of California, Berkeley, May 2010.
USENIX Association
USENIX Association
Depot: Cloud storage with minimal trust
Prince Mahajan, Srinath Setty, Sangmin Lee, Allen Clement, Lorenzo Alvisi, Mike Dahlin, and Michael Walfish
The University of Texas at Austin, fuss@cs.utexas.edu
Abstract
The paper describes the design, implementation, and
evaluation of Depot, a cloud storage system that mini-
mizes trust assumptions. Depot tolerates buggy or mali-
cious behavior by any number of clients or servers, yet it
provides safety and liveness guarantees to correct clients.
Depot provides these guarantees using a two-layer archi-
tecture. First, Depot ensures that the updates observed by
correct nodes are consistently ordered under Fork-Join-
Causal consistency (FJC). FJC is a slight weakening of
causal consistency that can be both safe and live despite
faulty nodes. Second, Depot implements protocols that
use this consistent ordering of updates to provide other
desirable consistency, staleness, durability, and recovery
properties. Our evaluation suggests that the costs of these
guarantees are modest and that Depot can tolerate faults
and maintain good availability, latency, overhead, and
staleness even when significant faults occur.
1 Introduction
This paper describes the design, implementation, and
evaluation of Depot, a cloud storage system in the spirit
of S3 [1], Azure [4], and Google Storage [3] but with a
crucial difference: Depot clients do not have to trust, that
is assume, that Depot servers operate correctly.
What motivates Depot is that cloud storage service
providers (SSPs), such as $3 and Azure, are fault-prone
black boxes operated by a party other than the data
owner. Indeed, clouds can experience software bugs [9],
correlated manufacturing defects [57], misconfigured
servers and operator error [53], malicious insiders [68],
bankruptcy [5], undiagnosed problems [14], Acts of God
(e.g., fires [20]) and Man [50]. Thus, it seems prudent
for clients to avoid strong assumptions about an SSP’s
design, implementation, operation, and status—and in-
stead to rely on end-to-end checks of well-defined prop-
erties. In fact, removing such assumptions promises to
help SSPs too: today, a significant barrier to adopting
cloud services is precisely that many organizations hesi-
tate to place trust in the cloud [18].
Given this motivation, Depot assumes less than any
prior system about the correctness of participating hosts:
¢ Depot eliminates trust for safety. A client can ensure
safety by assuming the correctness of only itself. De-
pot guarantees that any subset of correct clients ob-
serves sensible, well-defined semantics. This holds
regardless of how many nodes fail and no matter
whether they are clients or servers, whether these
are failures of omission or commission, and whether
these failures are accidental or malicious.
Depot minimizes trust for liveness and availability.
We wish we could say “trust only yourself” for live-
ness and availability. Depot does eliminate trust for
updates: a client can always update any object for
which it is authorized, and any subset of connected,
correct clients can always share updates. However, for
reads, there is a fundamental limit to what any storage
system can guarantee: if no correct, reachable node
has an object, that object may be unavailable. We cope
with this fundamental limit by allowing reads to be
served by any node (even other clients) while preserv-
ing the system’s guarantees, and by configuring the
replication policy to use several servers (which pro-
tects against failures of clients and subsets of servers)
and at least one client (which protects against tempo-
rary [8] and permanent [5, 14] cloud failures).
Though prior work has reduced trust assumptions in
storage systems, it has not minimized trust with respect
to safety, liveness, or both. For example, quorum and
replicated state machine approaches [15, 19, 30] toler-
ate failures by a fraction of servers. However, they sac-
rifice safety when faults exceed a threshold and live-
ness when too few servers are reachable. Fork-based
systems [12, 13, 43, 44] remain safe without trusting a
server, but they compromise liveness in two ways. First,
if the server is unreachable, clients must block. Second,
a faulty server can permanently partition correct clients,
preventing them from ever observing each other’s subse-
quent updates.
Indeed, it is challenging to guarantee safety and live-
ness while minimizing trust assumptions: without some
assumptions about correct operation, providing even a
weak guarantee like eventual consistency—the bare min-
imum of what a storage service should provide—seems
difficult. For example, a faulty storage node receiving an
update from a correct client might quietly fail to prop-
agate that update, thereby hiding it from the rest of the
system. Perhaps surprisingly, we find that eventual con-
sistency is possible in this environment.
In fact, Depot meets a contract far stronger than even-
tual consistency even under assorted and abundant faults
and failures. This set of well-defined guarantees under
weak assumptions is Depot’s top-level contribution, and
it derives from a novel synthesis of prior mechanisms and
our own. Depot is built around three key ideas:
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 710) 307
308
(1) Reduce misbehavior to concurrency. As in prior
work [12, 13, 43, 44], the protocol requires that an up-
date be signed and that it name both its antecedents and
the system state seen by the updater. Then, misbehavior
by clients or servers is limited to forking: showing di-
vergent histories to different nodes. However, previous
work detects but does not repair forks. In contrast, De-
pot allows correct clients to join forks, that is, to incorpo-
rate the divergence into a sensible history, which allows
them to keep operating in the face of faults. Specifically,
a correct node regards a fork as logically concurrent up-
dates by two virtual nodes. At that point, correct nodes
can handle forking by faulty nodes using the same tech-
niques [11, 23, 37, 61, 67] that they need anyway to han-
dle a better understood problem: logically concurrent up-
dates during disconnected operation.
(2) Enforce Fork-Join-Causal consistency. To allow
end-to-end checks on SSP behavior, we must specify
a contract: When must an update be visible to a read?
When is it okay for a read to “miss” a recent update? De-
pot guarantees that a correct client observes Fork-Join-
Causal consistency (FJC) no matter how many other
nodes are faulty. FJC is a slight weakening of causal
consistency [7, 40, 56]. Depot defines FJC as its consis-
tency contract because it is weak enough to enforce de-
spite faulty nodes and without hurting availability. At the
same time, FJC is strong enough to be useful: nodes see
each other’s updates in an order that reflects dependen-
cies among both correct and faulty nodes’ writes. This
ordering is useful not only for end users of Depot but
also internally, within Depot.
(3) Layer other storage properties over FJC. Depot
implements a layered architecture that builds on the or-
dering guarantees provided by FJC to provide other de-
sirable properties: eventual consistency, bounded stale-
ness, durability, high availability, integrity (ensuring that
only authorized nodes can update an object), snapshot-
ting of versions (to guard against spurious updates from
faulty clients), garbage collection, and eviction of faulty
nodes.! For all of these properties, the challenge is to
precisely define the strongest guarantee that Depot can
provide with minimal assumptions about correct opera-
tion. Once each property is defined, implementation is
straightforward because we can build on FJC, which lets
us reason about the order in which updates propagate
through the system.
The price of providing these guarantees is tolerable, as
demonstrated by an experimental evaluation of a proto-
type implementation of Depot. Depot adds a few hundred
bytes of metadata to each update and each stored object,
and it requires a client to sign and store each of its up-
dates. We demonstrate that Depot can tolerate faults and
!We are not explicitly addressing confidentiality and privacy, but,
as discussed in §3.1, existing approaches can be layered on Depot.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
maintain good availability, latency, overhead, and stale-
ness even when significant faults occur. Additionally, be-
cause Depot makes minimal assumptions about servers,
we can implement Teapot, a variation of Depot that pro-
vides many of Depot’s guarantees using an unmodified
SSP, such as Amazon’s S3. The difference between De-
pot and Teapot suggests several modest extensions to
SSPs’ interfaces that would strengthen their guarantees.
2 Why untrusted storage?
When we say that a component is untrusted, we are not
adopting a “tinfoil hat” stance that the component is op-
erated by a malicious actor, nor are we challenging the
honesty of storage service providers. What we mean is
that the system provides guarantees, usually achieved by
end-to-end checks, even if the given component is in-
correct. Since components could be incorrect for many
reasons (as stated in the introduction), we believe that
designing to tolerate incorrectness is prudence, not para-
noia. We now answer some natural questions.
SSPs are operated by large, reputable companies, so
why not trust them? That is like asking, “Banks are large,
reputable repositories of money, so why do we need bank
statements?” For many reasons, customers and banks
want customers to be able to check the bank’s view of
their account activity. Likewise, our approach might ap-
peal not only to customers but also to SSPs: by requiring
less trust, a service might attract more business.
How likely are faults in the SSP? We do not know
the precise probability. However, we know that providers
do fail (as mentioned in the introduction). More broadly,
they carry non-negligible risks. First, they are opaque (by
nature). Second, they are complex distributed systems.
Indeed, coping with known hardware failure modes in
local file systems is difficult [59]; in cloud storage, this
difficulty can only grow. Given the opacity and complex-
ity, it seems prudent not to assume the unfailing correct-
ness of an SSP’s internals.
Even if we do not assume that SSPs are perfect, the
most likely failure is the occasional corrupted or lost
block, which can be addressed with checksums and repli-
cation. Do you really need mechanisms to handle other
cases (that all of the nodes are faulty, that a fork happens,
that old or out-of-order data is returned, etc.)? Replica-
tion and checksums are helpful, and they are part of De-
pot. However, they are not sufficient. First, failures are
often correlated: as Vogels notes, uncorrelated failures
are “absolutely unrealistic ...as [failures] are often trig-
gered by external or environmental events” [69]. These
events include the litany in the introduction.
Second, other types of failures are possible. For exam-
ple, a machine that loses power after failing to commit its
output [52, 72] may lose recent updates, leading to forks
in history. Or, a network failure might delay propagation
USENIX Association
USENIX Association
I
| |Site1] | Site2]o oo Site p} |
I I
I |
it st
T
I
oo! |Sitel]ooo | Siteg
I
I
I
I
Storage service provider (SSP)
Oe Oo ; Customer 1’s volume 1
; : HO O-O Customer 1’s volume 2
eae ° :
2 °
° : °
Ngee ee 3
Customer n’s volume
Data center | Data center m
Fic. 1—Architecture of Depot. The arrows between servers
indicate replication and exchange.
of an update from one SSP node to another, causing some
clients to read stale data. In general, our position is that
rather than try to handle every possible failure individu-
ally, it is preferable to define an end-to-end contract and
then design a system that always meets that contract.
The above events seem unlikely. Is tolerating them
worth the cost? One of our purposes in this paper is to
report for the first time what that cost is. Whether to “pur-
chase” the guarantees is up to the application, but as the
price is modest, we anticipate, with hope, that many ap-
plications will find it attractive.
What about clients? We also minimize trust of clients
(since they are, of course, also vulnerable to faults).
3 Architecture, scope, and use
Figure 1 depicts Depot’s high-level architecture. A set of
clients stores key-value pairs on a set of servers. In our
target scenario, the servers are operated by a storage ser-
vice provider (SSP) that is distinct from the data owner
that operates the clients.2 Keys and values are arbitrary
strings, with overhead engineered to be low when values
are at least a few KB. A Depot client exposes an interface
of GET and PUT to its application users.
For scalability, we slice the system into groups of
servers, with each group responsible for one or more vol-
umes. Each volume corresponds to a range of one cus-
tomer’s keys, and a server independently runs the proto-
col for each volume assigned to it. Many strategies for
partitioning keys are possible [22, 36, 51], and we leave
2Because Depot does not require nodes to trust each other, different
data centers in Figure 1 could be operated by different SSPs. Doing so
might reduce the risk of correlated failures across replicas [6, 38]. For
simplicity, we describe and evaluate only single-SSP configurations.
the assignment of keys to volumes to layers above Depot.
The servers for each volume may be geographically
distributed, a client can access any server, and servers
replicate updates using any topology (chain, mesh, star,
etc.). As in Dynamo [22], to maximize availability, De-
pot does not require overlapping read and write quorums.
In fact, as the dotted lines suggest, Depot can even func-
tion under complete server unavailability: the protocol
permits clients to communicate directly with each other.
If the SSP later recovers, clients can continue using the
SSP (after sending the missed updates to the servers).
This raises a question: why have the SSP at all? We point
to the usual benefits of cloud services: cost, scalability,
geographic replication, and management.
We use the term node to mean either a client or a
server. Clients and servers run the same basic Depot pro-
tocol, though they are configured differently.
3.1 Issues addressed
One of our aims in this work is to push the envelope in the
trade-offs between trust assumptions and system guaran-
tees. Specifically, for a set of standard properties that one
might desire in a storage system, we ask: what is the min-
imum assumption that we need to provide useful guaran-
tees, and what are those guarantees? The issues that we
examine are as follows:
* Consistency (§4—-§5.2) and bounded staleness (§5.4):
Once a write occurs, the update should be visible to
reads “soon”. Consistency and staleness properties
limit the extent to which the storage system can re-
order, delay, or omit making updates visible to reads.
¢ Availability and durability (§5.3): Our availability
goal is to maximize the fraction of time that a client
succeeds in reading or writing an object. Durability
means that the system does not permanently lose data.
¢ Integrity and authorization (§5.5): Only clients autho-
rized to update an object should be able to create valid
updates that affect reads on that object.
¢ Data recovery (§5.6): Data owners care about end-to-
end reliability. Consistency, durability, and integrity
are not enough when the layers above Depot—faulty
clients, applications, or users—can issue authorized
writes that replace good data with bad. Depot does
not try to distinguish good updates from bad ones,
nor does it innovate on the abstractions used to de-
fend data from higher-layer failures. We do however
explore how Depot can support standard techniques
such as snapshots to recover earlier versions of data.
¢ Evicting faulty nodes (§5.7): If a faulty node provably
deviates from the protocol, we wish to evict it from the
system so that it will not continue to disrupt operation.
However, we must never evict correct nodes.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) 309
310
Depot provides the above properties with a layered
approach. Its core protocol (§4) addresses consistency.
Specifically, the protocol enforces Fork-Join-Causal con-
sistency (FJC), which is the same as causal consis-
tency [7, 40, 56] in benign runs. This protocol is the
essential building block for the other properties listed
above. In §5, we define these properties precisely and
discuss how Depot provides them.
Note that we explicitly do not try to solve the confiden-
tiality/privacy problem within Depot. Instead, like com-
mercial storage systems [1, 4], Depot enforces integrity
and authorization (via client signatures) but leaves it to
higher layers to use appropriate techniques for the pri-
vacy requirements of each application (e.g., allow global
access, encrypt values, encrypt both keys and values, in-
troduce artificial requests to thwart traffic analysis, etc.).
We also do not claim that the above list of issues is ex-
haustive. For example, it may be useful to audit storage
service providers with black box tests to verify that they
are storing data as promised [38, 62], but we do not ex-
amine that issue. Still, we believe that the properties are
sufficient to make the resulting system useful.
3.2 Depot in use: Applications & conflicts
Depot’s key-value store is a low-level building block over
which many applications can be built. For example, hun-
dreds of widely used applications—including backup,
point of sale software, file transfer, investment analytics,
cross-company collaboration, and telemedicine—use the
S3 key-value store [2], and Depot can serve all of them: it
provides a similar interface to S3, and it provides strictly
stronger guarantees.
An issue in systems that are causally consistent and
weaker—a set that includes not just Depot and S3
but also CVS, SVN, Git, Bayou [56], Coda [37], and
others—is handling concurrent writes to the same object.
Such conflicts are unfortunate but unavoidable: they are
provably the price of high availability [26].
Many approaches to resolving conflicting updates
have been proposed [37, 61, 67], and Depot does not
claim to extend the state of the art on this front. In fact,
Depot is less ambitious than some past efforts: rather
than try to resolve conflicts internally (e.g., by picking
a winner, merging concurrent updates, or rolling back
and re-executing transactions [67]), Depot simply ex-
poses concurrency when it occurs: a read of key k returns
the set of updates to k that have not been superseded by
any logically later update of k.*
This approach is similar to that of S3’s replication
3Note that Depot neither creates concurrency nor makes the prob-
lem worse. If an application cannot deal with conflicts, it can still use
Depot but must restrict its use (e.g., by adding locks and sending all
operations through a single SSP node), and it must sacrifice the ability
to tolerate faults (such as forks) that appear as concurrency.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
substrate, Dynamo [22], and it supports a range of
application-level policies. For example, applications us-
ing Depot may resolve conflicts by filtering (e.g., reads
return the update by the highest-numbered node, reads
return an application-specific merge of all updates, or
reads return all updates) or by replacing (e.g., the ap-
plication reads the multiple concurrent values, performs
some computation on them, and then writes a new value
that thus appears logically after, and thereby supersedes,
the conflicting writes).
3.3. System and threat model
We now briefly state our technical assumptions. First,
nodes are subject to standard cryptographic hardness as-
sumptions, and each node has a public key known to all
nodes. Second, any number of nodes can fail in arbitrary
(Byzantine [41]) ways: they can crash, corrupt data, lose
data, process some updates but not others, process mes-
sages incorrectly, collude, etc. Third, we assume that any
pair of timely, connected, and correct nodes can even-
tually exchange any finite number of messages. That is,
a faulty node cannot forever prevent two correct nodes
from communicating (but we make no assumptions about
how long “eventually” is).
Fourth, above we used the term correct node. This
term refers to a node that never deviates from the pro-
tocol nor becomes permanently unavailable. A node that
obeys the protocol for a time but later deviates is not
counted as correct. Conversely, a node that crashes and
recovers with committed state intact is equivalent to a
correct node that is slow. Fifth, to ensure the liveness of
garbage collection, we assume that unresponsive clients
are eventually repaired or replaced. To satisfy this as-
sumption, an administrator can install an unresponsive
client’s keys and configuration on new hardware [15].
4 Core protocol
In Depot, clients’ reads and updates to shared objects
should always appear in an order that reflects the logic of
higher layers. For example, an update that removes one’s
parents from a friend list and an update that posts spring
break photos should appear in that order, not the other
way around [21]. However, Depot has two challenges.
First, it aims for maximum availability, which fundamen-
tally conflicts with the strictest orderings [26]. Second, it
aims to provide its ordering guarantees despite arbitrary
misbehavior from any subset of nodes. In this section,
we describe how the protocol at Depot’s core achieves a
sensible and robust order of updates while optimizing for
availability and tolerating arbitrary misbehavior.
As mentioned above, this basic protocol is run by both
clients and servers. This symmetry not only simplifies
the design but also provides flexibility. For example, if
USENIX Association
USENIX Association
servers are unreachable, clients can share data directly.
For simplicity, the description below does not distinguish
between clients and servers.
4.1 Basic protocol
This subsection describes the basic protocol to propagate
updates, ignoring the problems raised by faulty nodes.
The protocol is essentially a standard log exchange pro-
tocol [10, 56]; we describe it here for background and to
define terms.
The core message in Depot is an update that changes
the value associated with a key. It has the following form:
dVV, {key, H(value), logicalClock@nodeID, H(history) }
nodeID
Updates are associated with logical times. A node as-
signs each update an accept stamp of the form logical-
Clock@nodeID [56]. A node N increments its logical
clock on each local write. Also, when N receives an up-
date u from another node, N advances its logical clock to
exceed u’s. Thus, an update’s accept stamp exceeds the
accept stamp of any update on which it depends [40]. The
remaining fields, dVV and H(history), and the writer’s
signature, Onoderp, defend against faults and are discussed
in subsections 4.2 and 4.3.
Each node maintains two local data structures: a log
of updates it has seen and a checkpoint reflecting the cur-
rent state of the system. For efficiency, Depot separates
data from metadata [10], so the log and checkpoint con-
tain collision-resistant hashes of values. If a node knows
the hash of a value, it can fetch the full value from an-
other node and store the full value in its checkpoint. Each
node sorts the updates in its log by accept stamp, sort-
ing first by logicalClock and breaking ties with nodelD.
Thus, each new write issued by a node appears at the end
of its own log and (assuming no faulty nodes) the log
reflects a causally consistent ordering of all writes.
Information about updates propagates through the sys-
tem when nodes exchange tails of their logs. Each node
N maintains a version vector VV with an entry for each
node M in the system: N.VV[M] is the highest logical
clock N has observed for any update by M [55]. To trans-
mit updates from node M to node N, M sends to N the
updates from its log that N has not seen.
Two updates are logically concurrent if neither ap-
pears in the other’s history. Concurrent writes may con-
flict if they update the same object; conflicts are handled
as described in Section 3.2.
4.2 Consistency despite faults
There are three fields in an update that defend the pro-
tocol against faulty nodes. The first is a history hash,
A (history), that encodes the history on which the update
depends using a collision-resistant hash that covers the
most recent update by each node known to the writer
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
when it issued the update. By recursion, this hash cov-
ers all updates included by the writer’s current version
vector. Second, each update is sent with a dependency
version vector, dVV, that indicates the version vector that
the history hash covers. Note that while dVV logically
represents a full version vector, when node N creates an
update u, u’s dVV actually contains only the entries that
have changed since the last write by N. Third, a node
signs its updates with its private key.
A correct node C accepts an update u only if it meets
five conditions. First, u must be properly signed. Sec-
ond, except as described in the next subsection, u must
be newer than any updates from the signing node that
C has already received. This check prevents C from ac-
cepting updates that modify the history of another node’s
writes. Third, C’s version vector must include u’s dVV.
Fourth, u’s history hash must match a hash computed
by C across every node’s last update at time dVV. The
third and fourth checks ensure that before receiving up-
date u, C has received all of the updates on which u de-
pends. Fifth, u’s accept stamp must be at most a constant
times C’s current wall-clock time (e.g., u.acceptStamp <
1000 * currentTimeMillis()). This check defends against
exhaustion of the 64-bit logical time space.
Given these checks, attempts by a faulty node to fabri-
cate u and pass it as coming from a correct node, to omit
updates on which u depends, or to reorder updates on
which u depends will result in C rejecting u. To compro-
mise causal consistency, a faulty node has one remaining
option: to fork, that is, to show different sequences of
updates to different communication partners [43]. Such
behavior certainly damages consistency. However, the
mechanisms above limit that damage, as we now illus-
trate with an example. Then, in subsection 4.3 we de-
scribe how Depot recovers from forks.
Example: The history hash in action A faulty node M
can create two updates uj;@y and uw @, such that neither
update’s history includes the other’s. M can then send
u;@m and the updates on which it depends to one node,
M1, and wu @y and its preceding updates to another node,
N2. N1 can then issue new updates that depend on up-
dates from one of M’s forked updates (here, u;@y) and
send these new updates to N2. At this point, absent the
history hash, N2 would receive N1’s new updates with-
out receiving the updates by M on which they depend: N2
already received u @y, SO its version vector appears to al-
ready include the prior updates. Then, if N2 applies just
N1’s writes to its log and checkpoint, multiple consis-
tency violations could occur. First, the system may never
achieve eventual consistency because N2 may never see
write “;@y. Further, the system may violate causality be-
cause N2 has updates from N1 but not some earlier up-
dates (e.g., u;@y) on which they depend.
The above confusion is prevented by the history hash.
311
312
If N1 tries to send its new updates to N2, N2 will be
unable to match the new updates’ history hashes to the
updates N2 actually observed, and N2 will reject N1’s
updates (and vice-versa). As a result, NJ and N2 will be
unable to exchange any updates after the fork junction
introduced by M after ujp@y.
Discussion At this point, we have composed mecha-
nisms from Bayou [56] and PRACTI [10] (update ex-
change), SUNDR [43] (signed version vectors), and
BFT2F [44] (history hashes, here used by clients and
modified to apply to history trees instead of linear his-
tories) to provide fork-causal consistency (FCC) under
arbitrary faults. We define FCC precisely in a technical
report [45]. Informally, it means that each node sees a
causally consistent subset of the system’s updates even
though the whole system may no longer be causally con-
sistent. Thus, although the global history has branched,
as each node peers backward from its branch to the be-
ginning of time, it sees causal events the entire way.
Unfortunately, enforcing even this weakening of
causal consistency would prohibit eventual consistency,
crippling the system: FCC requires that once two nodes
have been forked, they can never observe one another’s
updates after the fork junction [43]. In many environ-
ments, partitioning nodes this way is unacceptable. In
those cases, it would be far preferable to further weaken
consistency to ensure an availability property: connected,
correct nodes can always share updates. We now de-
scribe how Depot achieves this property, using a new
mechanism: joining forks in the system’s history.
4.3 Protecting availability: Joining forks
To join forks, nodes use a simple coping strategy: they
convert concurrent updates by a single faulty node into
concurrent updates by a pair of virtual nodes. A node that
receives these updates handles them as it would “normal”
concurrency: it applies both sets of updates to its state
and, if both branches modify the same object, it returns
both conflicting updates on reads (§3.2). We now fill in
some details.
Identifying a fork First consider a two-way fork. A fork
junction comprises exactly three updates where a faulty
node M has created two updates (e.g., wi@u and uj @y)
such that (i) neither update includes the other in its his-
tory and (ii) each update’s history hash links it to the
same previous update by that writer (e.g., uo@m). If a
node N2 receives from a node N1 an update whose his-
tory is incompatible with the updates it has already re-
ceived, and if neither node has yet identified the fork
junction, V1 and N72 identify the three forking updates as
follows. First, N1 and N2 perform a binary search on the
updates included in the nodes’ version vectors to identify
the latest version vector, VV, encompassing a common
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
history. Then, NV 1 sends its log of updates beginning from
VV.. Finally, at some point, N2 receives the first update
by M (e.g., ui1@m) that is incompatible with the updates
by M that N2 has already received (e.g., uo@m and u}@y)-
Tracking forked histories After a node identifies the
three updates in the fork junction, it expands its version
vector to include three entries for the node that issued the
forking updates. The first is the pre-fork entry, whose in-
dex is the index (e.g., M) before the fork and whose con-
tents will not advance past the logical clock of the last
update before the fork (e.g., uo@j). The other two are
the post-fork entries, whose indices consist of the index
before the fork augmented with the history hash of the re-
spective first update after the fork. Each of these entries
initially holds the logical clock of the first update after
the fork (e.g., of ui@y and u)@,,); these values advance
as the node receives new updates after the fork junction.
Note that this approach works without modification
if a faulty node creates a j-way fork, creating updates
Ute@m> “i@m> «+> Ur@m that link to the same prior up-
date (e.g., uo@mu). The reason is that, regardless of the
order in which nodes detect fork junctions, the branches
receive identical names (because branches are named by
the first update in the branch). A faulty node that is re-
sponsible for multiple dependent forks does not stymie
this construction either. After i dependent forks, a virtual
node’s index in the version vector is well-defined: it is
M || A(ujork,) || H(Ujork) |---| A eyore,) [56].
Log exchange revisited The expanded version vector
allows a node to identify which updates to send to a peer.
In the standard protocol, when a node N2 wants to re-
ceive updates from N1, it sends its current version vector
to N1 to identify which updates it needs. After N2 de-
tects a fork and splits one version vector entry into three,
it simply includes all three entries when asking N1 for
updates. Note that V1 may not be aware of the fork, but
the history hashes that are part of the indices of N2’s ex-
panded version vector (as per the virtual node construc-
tion above) tell N2 to which branch N1’s updates should
be applied and tell V1 which updates to actually send.
Conversely, if the sender N1 has received updates that
belong to neither branch, then N1 and N2 identify the
new fork junction as described above.
Bounding forks The overhead of this coping strategy
is the space, bandwidth, and computation needed for
fork detection and larger version vectors. Depot bounds
the number of forks that faulty nodes can introduce
by (1) making nodes “vouch” for updates by a forking
node that they had received before learning of the fork
and (2) making them promise not to communicate with
known forking nodes. We omit the details for space.
USENIX Association
USENIX Association
Safety/ Correct nodes
Dimension Liveness _ Property required
Consistency Safety Fork-Join Causal Any subset
Safety Bounded staleness Any subset
Safety Eventual consistency (s) | Any subset
Availability Liveness Eventual consistency (1) | Any subset
Liveness Always write Any subset
Liveness Always exchange Any subset
Liveness Write propagation Any subset
Liveness _ Read availability / A correct node
durability has object
Integrity Safety Only auth. updates Clients
Recoverability Safety Valid discard 1 correct client
Eviction Safety Valid eviction Any subset
Fic. 2—Summary of properties provided by Depot.
5 Properties and guarantees
This section describes how Depot enforces needed prop-
erties with minimal trust assumptions. Figure 2 summa-
rizes these properties and lists the required assumptions.
Below, we define these properties and describe how De-
pot provides them. The key idea is that the replication
protocol enforces Fork-Join-Causal consistency (FJC).
Given FJC, we can constrain and reason about the order
that updates propagate and use those constraints to help
enforce the remaining properties.
5.1 Fork-Join-Causal consistency
Clients expect a storage service to provide consistent ac-
cess to stored data. Depot guarantees a new consistency
semantic for all reads and updates to a volume that are
observed by any correct node: Fork-Join-Causal consis-
tency (FJC). A formal description of FJC appears in our
technical report [45]. Here we describe its core property:
¢ Dependency preservation. If update u; by a correct
node depends on an update ug by any node, then ug be-
comes observable before u, at any correct node. (An
update u of an object o is observable at a node if a read
of o would return a version at least as new as u [25].)
To explain FJC, we contrast it with causal consistency
(CC) in fail-stop systems [7, 40, 56]. CC is based on a
dependency preservation property that is identical to the
one above, except that it omits the “correct nodes” quali-
fication. Thus, to applications and users, FJC appears al-
most identical to causal consistency with two exceptions.
First, under FJC, a faulty node can issue forking writes w
and w’ such that one correct node observes w without
first observing w’ while another observes w’ without first
observing w. Second, under FJC, faulty nodes can issue
updates whose stated histories do not include all updates
on which they actually depend. For example, when cre-
ating the forking updates w and w’ just described, the
faulty node might have first read updates uc; and uc
from nodes C1 and C2, then created w that claimed to
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
depend on uc, but not uc2, and finally created update w’
that claimed to depend on uc but not uc). Note, however,
that once a correct node observes w (or w’), it will include
w (or w’) in its subsequent writes’ histories. Thus, as cor-
rect nodes observe each others’ writes, they will also ob-
serve both w and w’ and their respective dependencies in
a consistent way. Specifically, w and w’ will appear as
causally concurrent writes by two virtual nodes ($4.3).
Though FJC is weaker than linearizability, sequential
consistency, or causal consistency, it still provides prop-
erties that are critical to programmers. First, FJC implies
a number of useful session guarantees [66] for programs
at correct nodes, including monotonic reads, monotonic
writes, read-your-writes, and writes-follow-reads. Sec-
ond, as we describe in the subsections below, FJC is the
foundation for eventual consistency, for bounded stale-
ness, and for further properties beyond consistency.
Stronger consistency during benign runs. Depot
guarantees FJC even if an arbitrary number of nodes
fail in arbitrary ways. However, it provides a stronger
guarantee—causal consistency—during runs with only
omission failures. Of course, causal consistency itself is
weaker than sequential consistency or linearizability. We
accept this weakening because it allows Depot to remain
available to reads and writes during partitions [22, 26].
5.2 Eventual consistency
The term eventual consistency is often used informally,
and, as the name suggests, it is usually associated with
both liveness (“eventual”) and safety (“consistency”).
For precision, we define eventual consistency as follows.
¢ Eventual consistency (safety). Successful reads of an
object at correct nodes that observe the same set of
updates return the same values.
¢ Eventual consistency (liveness). Any update issued or
observed by a correct node is eventually observable by
all correct nodes.
The safety property is directly implied by FJC. The
liveness property is ensured by the replication proto-
col (§4), which entangles updates to prevent selective
transmission, and by the communication heuristics (§6),
which allow a node that is unable to communicate with a
server to communicate with any other server or client.
5.3 Availability and durability
In this subsection, we consider availability of reads, of
writes, and of update propagation. We also consider
durability. We begin by noting that the following strong
availability properties follow from the protocol in §4 and
the communication heuristics (§6):
¢ Always write. An authorized node can always update
any object.
313
314
¢ Always exchange. Any subset of correct nodes can ex-
change any updates that they have observed, assuming
they can communicate as per our model in §3.3.
¢ Write propagation. If a correct node issues a write,
eventually all correct nodes observe that write, assum-
ing that any message sent between correct nodes is
eventually delivered.
Unfortunately, there is a limit to what any storage sys-
tem can guarantee for reads: if no correct node has an ob-
ject, then the object may not be durable, and if no correct,
reachable node has an object, then the object may not be
available. Nevertheless, we could, at least in principle,
still have each node rely only on itself for read avail-
ability and durability: nodes could propagate updates and
values, and all servers and all clients could store all val-
ues. However, fully replicating all data is not appealing
for many cloud storage applications.
Depot copes with these limits in two ways. First, De-
pot provides guarantees on read availability and durabil-
ity that minimize the required number of correct nodes.
Second, Depot makes it likely that this number of cor-
rect nodes actually exists. The guarantees are as follows
(note that durability—roughly, “the system does not per-
manently lose my data’”—manifests as a liveness prop-
erty):
¢ Read availability. If during a sufficiently long syn-
chronous interval any reachable and correct node has
an object’s value, then a read by a correct node will
succeed.
¢ Durability. If any correct hoarding node, as defined
below, has an object’s value, then a read of that object
will eventually succeed. That is, an update is durable
once its value reaches a correct node that will not pre-
maturely discard it.
A hoarding node is a node that stores the value of a ver-
sion of an object until that version is garbage collected
($5.6). In contrast, a caching node may discard a value at
any time.
To make it likely that the premise of the guarantees
holds—namely that a correct node has the data—Depot
does three things. First, its configuration replicates data
to survive important failure scenarios. All servers usually
store values for all updates they receive: except as dis-
cussed in the remainder of this subsection, when a client
sends an update to a server and when servers transmit
updates to other servers, the associated value is included
with the update. Additionally, the client that issues an
update also stores the associated value, so even if all
servers become unavailable, clients can fetch the value
from the original writer. Such replication allows the sys-
tem to handle not only the routine failure case where a
subset of servers and clients fail and lose data but also the
client disaster and cloud disaster cases where all clients
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
or all servers fail [5, 14] or become unavailable [8].
Second, receipts allow a node to avoid accepting an
insufficiently-replicated update. When a server processes
an update and stores the update’s value, it signs a receipt
and sends the receipt to the other servers. Then, we ex-
tend the basic protocol to require that an update carry
either (a) a receipt set indicating that at least k servers
have stored the value or (b) the value, itself.
Thus, in normal operation, servers receive and store
updates with values, and clients receive and store updates
with receipt sets. However, if over some interval, fewer
than k servers are available, clients will instead receive,
store, and propagate both updates and values for updates
created during this interval. Finally, although servers nor-
mally receive updates and values together, there are cor-
ner cases where—to avoid violating the always exchange
property—they must accept an update with only a receipt
set. Thus, in the worst case Depot can guarantee only
that an object value not stored locally is replicated by the
client that created it and by at least k servers.
Third, if a client has an outstanding read for version
v, it withholds assent to garbage collect v ($5.6) until the
read completes with either v or a newer version.
5.4 Bounded staleness
A client expects that soon after it updates an object, other
clients that read the object see the update. The following
guarantee codifies this expectation:
¢ Bounded staleness. If correct clients C1 and C2 have
clocks that remain within A of a true clock and Cl
updates an object at time fo, then by no later than fo +
2Tann + Tprop + A, either (1) the update is observable
to C2 or (2) C2 suspects that it has missed an update
from C1.
Tonn and Tyyop are configuration parameters indicating
how often a node announces its liveness and how long
propagating such announcements is expected to take;
both are typically a few tens of seconds.
Depot uses FJC consistency to guarantee that a client
always either knows it has seen all recent updates or sus-
pects it has not. Every Tan, seconds, each client updates
a per-client beacon object [43] in each volume with its
current physical time. When C2 sees that C1’s beacon
object indicates time f, then C2 is guaranteed—by FJC
consistency—to see all updates issued by C1 before time
t. On the other hand, if C1’s beacon object does not show
a recent time, C2 suspects that it may not have seen other
recent updates by Cl.
When C2 suspects that it has missed updates from C1,
it switches to receiving updates from a different server.
If that does not resolve the problem, C2 tries to contact
C1 directly to fetch any missed updates and the updates
on which those missed updates depend.
USENIX Association
USENIX Association
Applications use the above mechanism as follows. If
anode suspects missing updates, then an application that
calls GET has two options. First, GET can return a warn-
ing that the result might be stale. This option is our de-
fault; it provides the bounded staleness guarantee above.
Alternatively, an application that prefers to trade worse
availability for better consistency [26] can retry with dif-
ferent servers and clients, blocking until the local client
has received all recent beacons.
Note that a faulty client might fail to update its beacon,
making all clients suspicious all the time. What, then, are
the benefits of this bounded staleness guarantee? First,
although Depot is prepared for the worst failures, we ex-
pect that it often operates in benign conditions. When
clients, servers, and the network operate properly, clients
are given an explicit guarantee that they are reading fresh
data. Second, when some servers or network paths are
faulty, suspicion causes clients to fail-over to other com-
munication paths to get recent updates.
Bounded staleness v. FJC. Bounded staleness and
FJC consistency are complementary properties in Depot.
Without bounded staleness, a faulty server could serve a
client an arbitrarily old snapshot of the system’s state—
and be correct according to FJC. Conversely, bounding
staleness without a consistency guarantee (assuming that
is even possible; we bound staleness by relying on con-
sistency) is not enough. For engineering reasons, our
staleness guarantees are tens of seconds; absent consis-
tency guarantees, applications would get confused be-
cause there could be significant periods of time when
some updates are visible, but related ones are not.
5.5 Integrity and authorization
Under Depot, no matter how many nodes are faulty, only
authorized clients can update a key/value pair in a way
that affects correct clients’ reads: the protocol requires
nodes to sign their updates, and correct nodes reject
unauthorized updates.
A natural question is: how does the system know
which nodes are authorized to update which objects? Our
prototype takes a simple approach. When a volume is
created, it is statically configured to associate ranges of
lookup keys with specific nodes’ public keys. This al-
lows specific clients to write specific subsets of the sys-
tem’s objects, and it prevents servers from modifying
the objects that they store on behalf of clients. Imple-
menting more sophisticated approaches to key manage-
ment [48, 71] is future work. We speculate that Depot’s
FJC consistency will make it relatively easy to ensure
a sensible ordering of policy updates and access control
decisions [24, 71].
5.6 Data recovery
Even if a storage system retains a consistent and fresh
view of the data written to it, data owners care about end-
to-end reliability, and the applications and users above
the storage system pose a significant risk. For example,
many of the failures listed in the introduction may cor-
rupt or destroy valuable data. Depot does not attempt to
distinguish “good” updates from “bad” ones or advance
the state of the art in protecting storage systems from bad
updates. Depot’s FJC consistency does, however, provide
a basis for applying many standard defenses. For exam-
ple, Depot can keep all versions of the objects in a vol-
ume, or it can provide a basic laddered backup scheme
(all versions of an object kept for a day, daily versions
kept for a week, weekly versions kept for a month, and
monthly versions kept for a year).
Given FJC consistency, implementing laddered back-
ups is straightforward. Initially, servers retain every up-
date and value that they receive, and clients retain the
update and value for every update that they create.
Then, servers and clients discard the non-laddered ver-
sions by unanimous consent of clients. Every day, clients
garbage collect a prefix of the system’s logs by produc-
ing a checkpoint of the system’s state (using techniques
adopted from Bayou [56]). The checkpoint includes in-
formation needed to protect the system’s consistency and
a candidate discard list (CDL) that states which prior
checkpoints and which versions of which objects may be
discarded. The job of proposing the checkpoint rotates
over the clients each day.
The keys to correctness here are (a) a correct client
will not sign a CDL that would delete a checkpoint pre-
maturely and (b) a correct node discards a checkpoint or
version if and only if it is listed in a CDL signed by all
clients. These checks ensure the following property:
¢ Valid discard. If at least one client is correct, a correct
node will never discard a checkpoint or a version of an
object required by the backup ladder.
Note that a faulty client cannot cause the system to
discard data that it needs: the above approach provides
the same read availability and durability guarantees for
backup versions as for the current version ($5.3). How-
ever, a faulty client can delay garbage collection. If a
checkpoint fails to garner unanimous consent, clients no-
tify an administrator, who troubleshoots the faulty client
or, if all else fails, replaces it with a new machine. Thus,
faulty clients can cause the system to consume extra
storage—but only temporarily, assuming that unrespon-
sive clients are eventually repaired or replaced (§3.3).
5.7 Evicting faulty nodes
Depot evicts nodes that provably deviate from the proto-
col (e.g., by issuing forking writes) and ensures:
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) 315
316
¢ Valid eviction. No correct node is ever evicted.
For space, we discuss eviction only at a high level;
details are in our technical report [45]. We use proofs of
misbehavior (POMs): because nodes’ updates are signed,
many misbehaviors are provable as such. For example,
when a node N observes forking writes from a faulty
node M, it creates a POM and slots the POM into the
update log, ensuring that the POM will propagate. Note
that eviction occurs only if there is a true proof of mis-
behavior. If a faulty node is merely unresponsive, that is
handled exactly as SLA violations are today.
6 Implementation
Our Depot prototype is implemented in Java. It keeps ev-
ery version written so does not implement laddered back-
ups or garbage collection (§5.6). It is otherwise com-
plete (but not optimized). The prototype uses Berkeley
DB (BDB) for local storage and does so synchronously:
after writing to BDB, Depot calls commit before return-
ing to the caller, and we configure BDB to call fsync on
every commit.*
Implementation of GET & PUT. Depot clients expose
a PUT and GET API and implement these calls over the
log exchange protocol (§4). Recall that Depot separates
data from metadata and that an update is only the meta-
data. Each client node chooses a (usually nearby) pri-
mary server and fetches updates via background gossip.
On a PUT, a client first locally stores the update and
value. As an optimization, rather than initiate the log ex-
change protocol, a client just sends the update and value
of each PUT directly to its primary server. If the update
passes all consistency checks and the value matches the
hash in the update, the server adds these items to its
log and checkpoint. Otherwise, the client and server fall
back on log exchange. Similarly, servers send updates
and bodies to each other “out of band” as they are re-
ceived; if two servers detect that they are out of sync,
they fall back on log exchange.
On a GET, a client sends the requested lookup key,
k, to its primary server along with a staleness hint. The
staleness hint is a set of two-byte digests, one per log-
ically latest update of k that the client has received via
background gossip; note that unless there are concurrent
updates to k, the staleness hint contains one element. If
the staleness hint matches the latest updates known to
the server, the server responds with the corresponding
values. The client then checks that these values corre-
spond to the H(value) entries in the previously received
updates. If so, the client returns the values to the appli-
4This approach aids, but does not quite guarantee, persistence of
committed data: “synchronous” disk writes in today’s systems do not
always push data all the way to the disk’s platter [52]. Note that if a
node commits data and subsequently loses it because of an ill-timed
crash, Depot handles that case as it does with any other faulty node.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
Depot’s overheads are modest. E.g., for 1OKB requests 99%-tile
latency for GET falls from 2.1 ms to 1.6 ms; for PUT itincreases $7.1
from 14.8 ms to 27.7 ms.
Depot imposes little additional cost for read-mostly workloads. |
For example, Depot’s weighted dollar cost of 1OKB GETs and §7.2
PUTs are 2% and 56% higher than the baseline.
Depot continues correct operation when failures occur with lit- _g7.3
tle impact on latency or resource consumption.
Fic. 3—Summary of main evaluation results.
Baseline Clients trust the server to handle their PUTs and
GETs correctly. Clients neither maintain local state
nor perform checks on returned data.
B+Hash Clients attach SHA-256 hashes to the values that
they PUT and verify these hashes on GETs.
B+H+Sig Clients sign the values that they PUT and verify
these signatures on GETs.
B+H+S+Store The same checks as B+H+Sig, plus clients locally
store the values that they PUT, for durability and
availability despite server failures.
FIG. 4—Baseline variants whose costs we compare to Depot’s.
cation, completing the GET. If the server rejects the stal-
eness hint or if the values do not match, then the client
initiates a value and update transfer by sending to its pri-
mary server (a) its version vector and (b) k. The server
replies with (a) the missing updates, which the client ver-
ifies ($4.2), and (b) the most recent set of values for k.
If a client cannot reach its primary server, it randomly
selects another server (and does likewise if it cannot
reach that server). If no servers are available, the client
enters “client-to-client mode” for a configurable length
of time, during which it gossips with the other clients. In
this mode, on a PUT, the client responds to the applica-
tion as soon as the data reaches the local store. On a GET,
the client fetches the values from the clients that created
the latest known updates of the desired key.
7 Experimental evaluation
The principal question that drives our evaluation is: what
is the “price of distrust?” That is, how much do Depot’s
guarantees cost, relative to the costs of a baseline storage
system? We measure latency, network traffic, storage at
both clients and servers, and CPU cycles consumed at
both clients and servers (§7.1). We then convert the re-
source overheads into a common currency [29] using a
cost model loosely based on the prices charged by to-
day’s storage and compute services (§7.2). We then move
from “stick” to “carrot”, illustrating Depot’s end-to-end
guarantees (§7.3). Figure 3 summarizes our results.
Method and environment Most of our experiments
compare our Depot implementation to a set of baseline
key-value storage systems, described in Figure 4. All of
them replicate the key-value pairs to a set of servers, us-
USENIX Association
USENIX Association
Baseline =
B+Hash zz
BHHsSig ==
B+H+S+Store SSS
100 Dept ==
a
50 [
7 a tl il Hl
3B/GET 3B/PUT 10KB/GETIOKB/PUT 1MB/GET 1MB/PUT
latency (ms)
(a)
99 percentile latency (ms)
200 Baseline =
B+Hash zzz
BHHsSig ==
B+H+S+Store
Depot =
160
120
80
40
a8 al |
3B/GET 3B/PUT 10KB/GETIOKB/PUT 1MB/GET IMB/PUT
(b)
FIG. 5—Latencies ((a) mean and standard deviation and (b) 99th percentile) for GETs and PUTs for various object sizes in Depot
and the four baseline variants. For small- and medium-sized requests, Depot introduces negligible GET latency and sizeable latency
on PUTs, the extra overhead coming from signing, synchronously storing a local copy, and Depot’s additional checks. For large
requests, collision-resistant hashing adds significant latency to both PUTs and GETs.
ing version vectors to detect precedence, but omit one or
more of Depot’s safeguards. In none of the variants do
clients check version vectors or maintain history hashes.
We have implemented these baseline variants using the
same code base as Depot, so they are not heavily opti-
mized. For example, as in Depot, the baselines separate
data from metadata, causing writes to two Berkeley DB
tables on every PUT, which is possibly inefficient com-
pared to a production storage system. Such inefficiencies
may lead to our underestimating Depot’s overhead.
Our default configuration is as follows. There are 8
clients and 4 servers with the servers connected in a mesh
and two clients connecting to each server. Servers gossip
with each other once per second; a client gossips with its
primary server every 5 seconds. We experiment with a
slightly older implementation that runs without receipts
(§5.3) and beaconing (§5.4).
Our default workload is as follows. Clients issue a se-
quence of PUTs and GETs against a volume preloaded
with 1000 key-value pairs. We partition the write key set
into several non-overlapping ranges, one for each client.
As a result, a GET returns a single value, never a set. A
client chooses write keys randomly from its write key
range and read keys randomly from the entire volume.
We fix the key size at 32 bytes. In each run, each client is-
sues 600 requests at roughly one request per second. We
examine three different value sizes (3 bytes, 10 KB, and
1 MB) and the following read-write percentages: 0/100,
10/90, 50/50, 90/10, and 100/0. (We do not report the
10/90 and 90/10 results; their results are consistent with,
and can be predicted by, those from the other workloads).
We use a local Emulab [70]. All hosts run Linux
FC 8 (version 2.6.25.14-69) and are Dell PowerEdge
1200 servers, each with a quad-core Intel Xeon X3220
2.40 GHz processor, 8 GB of RAM, two 7200RPM local
disks, and one | Gigabit Ethernet port.
7.1 Overhead of Depot
Latency To evaluate latencies in Depot and the baseline
systems, we measure from the point of view of the appli-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
cation, from when it invokes GET or PUT at the local li-
brary until that call returns. Note that for a PUT, the client
commits the PUT locally (if it is a Depot or B+H+S+Store
client) and only then contacts the server, which replies
only after committing the PUT. We report means, stan-
dard deviations, and 99th percentiles, from the GET (i.e.,
100/0) and PUT (i.e., 0/100) workloads.
Figure 5 depicts the results. For the GET runs, the
difference in means between Baseline and B+Hash are
0.0, 0.2, and 15.2 ms for 3B, 1OKB, and 1MB, respec-
tively, which are explained by our measurements of mean
SHA-256 latencies in the cryptographic library that De-
pot uses: 0.1, 0.2, and 15.7 ms for those object sizes.
Similarly, the means of RSA-Verify operations explain
the difference between B+Hash and B+H+Sign for 3B
and 10KB, but not for 1MB; we still investigating over-
heads for that latter case. Note that Depot’s GET latency
is lower than that of the strongest two baselines. The rea-
son is that Depot clients verify signatures in the back-
ground, whereas the baseline variants do so on the criti-
cal path. A key observation is that, for GETs, Depot does
not introduce much latency beyond applying a collision-
resistant hash to data stored in an SSP—which prudent
applications likely do anyway.
For PUTs, the latency is higher. Each step from
B+Hash to B+H+S to B+H+S+Store to Depot adds sig-
nificantly to mean latency, and for large requests, going
from Baseline to B+Hash does as well. For example, the
mean latency for 1|OKB PUTs ascends 3.8 ms, 3.9 ms,
8.5 ms, 9.7 ms, 13.0ms as we step through the sys-
tems; 99%-tile latency goes 14.8 ms, 15.1 ms, 20.4 ms,
37.9 ms, 27.8 ms.
We can explain the observed Depot PUT latency with
a simple model. Depot handles PUTs serially, so we sum
the overheads of a PUT’s components (microbenchmark
means for 1OKB are in parentheses, with estimates de-
noted =): the client hashes the value (0.2 ms), hashes
history (~ 0.1 ms), signs the update (4.2 ms), stores the
body (2.6 ms, with the DB cache enabled), stores the up-
date (= 1.5 ms), and transfers the update and body over
317
318
1.4
i 1.2 - ig PPO Oory
8 1.0 r yy
Eos y
E 0.6 N
2 0.4 7 N
& 02 J N
I
0.0
NW/GET (C-S) NW/PUT (C-S) NW/PUT (S-S) _ Stor/Ver (C)
(KB) (KB) (KB) (KB)
Stor/Ver (S)
L
MM NOD
KSSSASNS
Cia
=!
CPU/GET (C) CPU/GET(S) CPU/PUT(C) CPU/PUT (S)
(KB) (ms) (ms) (ms) (ms)
FIG. 6—Resource use of Baseline, B+Hash, B+H+Sig, B+H+S+Store, and Depot. The bar heights represent resource use normal-
ized to Depot, for 10 KB objects and the 100/0 and 0/100 workloads. The labels indicate the actual values. (C) and (S) indicate the
average per-request resource use at clients and servers, respectively. (C-S) and (C-S) are client-server and server-server network
use, respectively. For storage costs (labeled Stor/Ver), we report the cost of storing a version of an object.
the | Gbps network (* 0.1 ms); the server verifies the
signature (0.3 ms), hashes the value (0.2 ms), hashes his-
tory (= 0.1 ms), and stores the body (2.6 ms) and update
(= 1.5 ms). The sum of the means (13.4 ms) is close to
the observed latency of 13.0 ms. The model is similarly
accurate for the 3B experiments but off by 20% for 1MB;
we hypothesize that the divergence stems from queues
that build in front of BDB during periodic log exchange.
These PUT latencies could be reduced. For example,
we have not exploited obvious pipelining opportunities.
Also, we experiment on a | Gbit/s LAN; in many cloud
storage deployments, WAN delays would dominate la-
tencies, shrinking Depot’s percentage overhead.
Resource utilization Figure 6 depicts the overheads of
various resources in the experiments run above. Depot’s
overheads are small for GET bandwidth and CPU, for
server-server bandwidth, and for server storage cost. The
PUT client-server bandwidth overheads are about 20%.
The PUT client CPU overheads are substantial due to
the additional Berkeley DB access and cryptographic
checks. Client storage overheads are also substantial due
to the added requirement that clients store data for the
PUTs that they create and metadata for all PUTs.
7.2 Dollar cost
Different resources have different costs. To characterize
Depot’s overall cost, we convert the measured overheads
from the prior subsection into dollars. We use the follow-
ing cost model, loosely based on what customers pay to
use existing cloud storage and compute resources.
Client-server network bandwidth $.10/GB
Server-server network bandwidth $.01/GB
Disk storage (one client or server) $.025/GB per month
CPU processing (client or server) $.10 per hour
Figure 7 shows the overheads from Figure 6 weighted
by these costs. Depot’s overheads are modest for read-
mostly workloads. Depot’s GET costs are only slightly
higher than Baseline’s: $108.10 v. $106.50 for 10° GET
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
250 Baseline
B+Hash
B+H+Sig
B+H+S+Store
200
150
Cost ($/TB)
100
50
PUT (TB)
GET (TB) Store (TB-month)
FIG. 7—Dollar cost to GET 1TB of data, PUT 1TB of data, or
store 1TB of data for 1 month. Each object has a small key
and a 10KB value. 1TB of PUTs or GETs corresponds to 10°
operations, and 1TB of storage corresponds to 10° objects.
operations on 1OKB objects. However, Depot’s PUT costs
are over 50% higher: $234.40 v. $150.50 for 10° opera-
tions on LOKB objects. Most of the extra cost is from dis-
tributing and verifying metadata across all nodes, so the
relative overheads would fall for larger objects. Depot’s
storage costs are 31% higher than Baseline’s: $138.50 v.
$105.50 to store 10° 10KB objects for a month. Most of
the extra cost is from storing a copy of each object at the
issuing client; the rest is from storing metadata.
7.3. Experiments with faults
We now examine Depot’s behavior when servers become
unavailable and when clients create forking writes.
Server unavailability In this experiment, 8 clients ac-
cess 8 objects on 4 servers. The objects are 1OKB, and
the workload is 50/50 GET/PUT. Servers gossip with ran-
dom servers every second, and clients gossip with their
chosen partner (initially a server) every 5 seconds. 300
seconds into the experiment, we stop all servers. By post-
processing logs, we measure the staleness of GET results,
compared to instantaneous propagation of all updates:
the staleness of a GET’s result is the time since that re-
sult was overwritten by a later PUT. If the GET returns
the most recent update, the staleness is 0.
USENIX Association
USENIX Association
B+H+S+Store
staleness (sec)
0 100 200 300 400 500 600
time (seconds)
(a)
60
Depot soinesetatonetivedste
50 B+H+S+Store
40 :
GET latency (ms)
0 100 200 300 400 500 600
time (seconds)
(b)
FIG. 8—The effect of total server failure (t = 300) on (a) staleness and (b) latency. The workload is 50/50 R/W and 10KB objects.
For space, we omit the graph of PUT latency for this experiment. Depot maintains availability through client-to-client transfers
whereas the baseline system blocks, and GET latency actually improves (at the expense of staleness).
60
50
40
30
20
10
0
GET latency (ms)
0 100 200 300 400 500 600
time (seconds)
FIG. 9—GET latency seen by a correct client in three runs:
8 correct clients (8COF), 6 correct clients and 2 faulty clients
(6C2F), and 6 correct clients (6COF). The results for PUT la-
tency are not depicted but are the same: Depot survives forks
without affecting client-perceived latency.
Figure 8(a) depicts the staleness observed at one
client. Before the servers fail, GETs in both Depot
and B+H+S+Store have low staleness. After the failure,
B+H+S+Store blocks forever. Depot, however, switches
to client-to-client mode, continuing to service requests.
Staleness increases noticeably both because it takes more
network hops to disseminate updates and because the
lower gossip frequency increases the delay between
hops.
Figure 8(b) depicts the latency of GETs observed by
the same client. Prior to the failure, Depot’s GET latency
is significantly higher than measured in the experiments
in §7.1 because the workload here has just 8 objects, each
of which is updated every 2 seconds, so the optimization
described in §6 often fails, forcing the client and server
to perform a log exchange before the GET can complete.
When the servers fail, Depot continues to function, and
GET latency actually improves: rather than requesting
“the current” value from the server (and then completing
a log exchange to get the new metadata required to vali-
date the newest update), in client-to-client mode, a client
fetches the specific version mentioned in the update it
already has from the writer. Though not depicted, De-
pot’s PUT latency also improves in client-to-client mode:
PUT operations return as soon as the update and value are
stored locally, with no round trip to a server.
Client fork In this experiment, 8 correct clients (8COF),
6 correct clients and 2 faulty clients (6C2F), and 6 correct
clients (6COF) access 1000 objects on 4 servers. The ob-
jects are 1OKB, and the workload is 50/50 GET/PUT. 300
seconds into the experiment, faulty clients begin to issue
forking writes. When a correct client observes a fork,
it creates and publishes a proof of misbehavior (POM)
against the faulty client, and when servers or other clients
receive the POM, they stop accepting new writes directly
from the faulty client.
Figure 9 depicts the results for GETs. Forks introduced
by faulty clients do not have obvious effect on GET or
PUT latency; note that the spikes in GET latency prior to
t = 300 are unrelated to client failures. We also measured
CPU consumption and found no interesting differences
among the intervals before the failures, at the time of the
failures, or after the faulty nodes had been evicted.
8 Teapot for legacy SSPs
Depot runs on both clients and SSP nodes, but it would
be desirable to provide Depot’s guarantees using unmod-
ified legacy SSPs such as S3, Azure Storage, or Google
Storage. Intuitively, such an approach appears possible.
In Depot, servers must (1) propagate updates among
clients and (2) provide update bodies (i.e., values) in re-
sponse to GET requests. We should be able to use an
SSP’s abstract key-value map as a communication chan-
nel and as storage for update bodies. And because Depot
clients verify everything that they receive from servers,
we should still be able to provide most of the properties
discussed in §5. In this section, we give a brief overview
of Teapot, a variation of Depot that uses legacy SSPs.
Teapot assumes an API like that of S3: LPUT(k, v, b)
(associate v with k in a bucket b owned by a given client)
and LGET(k,b) (return v). On a PUT, the Teapot client
creates and locally stores the metadata u (a Depot up-
date) and the data d (a Depot value). The client then
stores both to the SSP by calling LPUT(H(u), u,b.) and
LPUT(H(d),d,b-), where b, is a bucket that only c can
write. The client then identifies its latest update by stor-
ing it to a distinguished key, k* (that is, the client exe-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) 319
320
600 Raw $3 PUT/GET ==
Teapot PUT/GET z=
500
latency (ms)
Teapot GET Teapot PUT
Fic. 10—Average latencies (with standard deviations) per-
ceived by Teapot for GET and PUT operations with 10KB pay-
load when using Amazon S3 for storage.
cutes LPUT(k*, u, b.)). In the background, the client peri-
odically fetches the other clients’ latest updates by read-
ing their k* entries and then fetching and validating the
updates’ dependencies. On a GET, the Teapot client uses
LGET to retrieve the value(s) associated with the latest
update(s) that it has received.
We have prototyped Teapot using S3 and a variation on
the arrangement just sketched. As shown in Figure 10,
accessing $3 through Teapot rather than through LPUT
and LGET introduces little latency over S3; the baseline
latencies to S3 are already scores of milliseconds, so the
additional overheads are small. The other resource costs
(client-side storage, extra bandwidth, etc.) are similar to
those of Depot (§7.1).
Discussion Teapot differs from Depot in two important
ways. First, if a client fails in particular ways, Teapot
cannot guarantee valid discard (§5.6). A client c can,
for example, issue a PUT, allow the update to be ob-
served by other clients, and then delete the value associ-
ated with the update. Second, Teapot servers cannot pro-
vide the durability receipts that Depot clients use to avoid
depending on insufficiently-replicated data (§5.3). Note
that Teapot tolerates arbitrary SSP failures and many
other client failures (crashes, forks, etc.), so Teapot’s ad-
ditional vulnerability over Depot is limited and may be
justified by its deployability.
We now ask: what incremental extensions to SSPs
would allow us to run code only on clients but recover
Depot’s full guarantees? We speculate that the following
suffices. First, to allow a correct client to avoid depend-
ing on updates that a faulty client could delete, the SSP
could implement LINK(K,b.,b-’), UNLINK(k, bc, be),
and VERIFY (k, H, b,). LINK causes every existing or new
key/value pair in a keyrange K in one client’s bucket
(b.) to be linked to another client’s bucket (b,’), where
a key/value pair linked to another bucket may not be
modified or deleted. UNLINK removes such a link. VER-
IFyY checks that the SSP stores a value with hash H
for key k in bucket b,. Then, if a client links to other
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
clients’ buckets when it joins the system and it veri-
fies an update’s value before accepting the update into
its history, we can effectively restore unanimous consent
for garbage collecting versions (§5.6). Second, to assure
clients that updates are sufficiently replicated, the SSP
could return a receipt in response to LPUT that the clients
could use like receipt sets in standard Depot (§5.3).
These extensions seem plausible. Others have proposed
receipts [38, 58, 62, 74], and the proposed LINK and UN-
LINK calls have correlates on Unix file systems, suggest-
ing utility beyond Teapot.
This discussion illustrates that clients can use an SSP-
supplied key-value map as a black box to recover most of
Depot’s properties. To recover all of them, the SSP needs
to be incrementally augmented not to delete prematurely.
9 Related work
We organize prior work in terms of trade-offs between
availability and fault-tolerance.
Restricted fault-tolerance, high availability. A num-
ber of systems provide high availability but do not tol-
erate arbitrary faults. For example, key-value stores in
clouds [16, 21, 22] take a pragmatic approach, using
system structure and relaxed semantics to provide high
availability. Also, systems like Bayou [67], Ficus [61],
PRACTI [10], and Cimbiosys [60] can get high avail-
ability by replicating all data to all nodes. Unlike Depot,
none of these systems tolerates arbitrary failures.
Medium fault-tolerance, medium availability. An-
other class of systems provides safety even when only
a subset (for example, 2/3 of the nodes) is correct. How-
ever, the price for this increased fault tolerance compared
to the prior category is decreased liveness and availabil-
ity: to complete, an operation must reach a quorum of
nodes. Such systems include Byzantine-Fault Tolerant
(BFT) replicated state machines (see [15, 19, 30, 33])
and Byzantine Quorums [46]. Note that researchers are
keenly interested in reducing trust: compared to clas-
sic BFT systems, the recently proposed A2M [17],
TrInc [42], and BFT2F [44] all tolerate more failures, the
former two by assuming trusted hardware and the latter
by weakening guarantees. However, unlike Depot, these
systems still have fault thresholds, and none works dis-
connectedly. PeerReview [31] requires a quorum of wit-
nesses with complete information (hindering liveness),
one of which must be correct (a trust requirement that
Depot does not have).
High fault-tolerance, low availability. In fork-based
systems, such as SUNDR [43] and FAUST [12], the
server is totally untrusted, yet even under faults provides
a safety guarantee: fork-linearizability, fork-sequential
consistency, etc. [54]. However, these systems provide
reduced liveness and availability compared to Depot.
USENIX Association
USENIX Association
First, in benign runs, their admittedly stronger seman-
tics means that they cannot be available during a network
partition or server failure. Second, after a fork, nodes are
“stranded” and cannot talk to each other, effectively stop-
ping the system. A related strand of work focuses on ac-
countability and auditing (see [38, 58, 62, 74]), providing
proofs to participants if other participants misbehave. All
of these systems detect misbehavior, whereas our aim is
to tolerate and recover from it—which we view as a re-
quirement for availability.
Systems with similar motivations. Venus [63] allows
clients not to trust a cloud storage service. While Venus
provides consistency semantics stronger than Depot’s
(causal consistency for pending operations, lineariz-
ability for completed operations (roughly)), it makes
stronger assumptions than Depot. Specifically, Venus re-
lies on an untrusted verifier in the cloud; assumes that a
core set of clients does not permanently go offline; and
does not handle faulty clients, such as clients that split
history. SPORC [24] is designed for clients to use a sin-
gle untrusted server to order their operations on a sin-
gle shared document and provides causal consistency for
pending operations (and stronger for committed opera-
tions). Unlike Depot, SPORC does not consider faulty
clients, allow clients to talk to any server, or support arbi-
trary failover patterns. However, SPORC provides innate
support for confidentiality and access control, whereas
Depot layers those on top of the core mechanism.
A number of other systems have sought to minimize
trust for safety and liveness. However, they have not
given a correctness guarantee under arbitrary faults. For
example, Zeno [64] does not operate with maximum live-
ness or minimal trust assumptions: it assumes f + | avail-
able servers per partition, where f is the number of faulty
servers. TimeWeave [47] ensures that correct nodes can
pass the blame of any mal-activity to culprit nodes, and
S2D2 [35] uses tamper-evident history summaries to de-
tect forks. However, unlike Depot, these two systems
neither repair forks nor target cloud storage (which re-
quires addressing staleness, durability, and recoverabil-
ity). Other systems target scenarios similar to cloud stor-
age but do not protect consistency [28, 34, 65].
Some systems have, like Depot, been designed to re-
sist large-scale correlated failures. Glacier [32] can tol-
erate a high threshold, but still no more than this thresh-
old, of faulty nodes, and it stores only immutable objects.
OceanStore [39] is designed to minimize trust for dura-
bility but does not tolerate nodes that fail perniciously.
Distributed revision control. Distributed repositories
like Git [27], Mercurial [49], and Pastwatch [73] in-
corporate a data model similar to Depot’s, and could
be augmented to resist faulty nodes (for example, forc-
ing clients to sign updates in Git would prevent servers
from undetectably altering history). However, all of these
systems are fundamentally geared toward replicating a
source code repository. Our context brings concerns that
these systems do not address, including how to avoid
clients’ storing all data, how to perform update exchange
in this scenario, how to provide freshness, how to evict
faulty nodes, how to garbage collect, etc.
10 Conclusion
Depot began with an attempt to explore a radical point
in the design space for cloud storage: trust no one. Ulti-
mately we fell short of that goal: unless all nodes store
a full copy of the data, then nodes must rely on one an-
other for durability and availability. Nonetheless, we be-
lieve that Depot significantly expands the boundary of
the possible by demonstrating how to build a storage sys-
tem that eliminates trust assumptions for safety and min-
imizes trust assumptions for liveness.
Acknowledgments
Insightful comments by Marcos K. Aguilera, Hari Bal-
akrishnan, Brad Karp, David Maziéres, Arun Seehra, Jes-
sica Wilson, the anonymous reviewers, and our shepherd,
Michael Freedman, improved this paper. The Emulab
staff was a great help, as always. This work was sup-
ported by ONR grant N00014-09-10757, AFOSR grant
FA9550-10-1-0073, and NSF grant CNS-0720649.
References
[1] Amazon Simple Storage Service (Amazon S3).
http://aws.amazon.com/s3.
[2] AWS forum: Customer app catalog.
http://developer.amazonwebservices.com/connect/
kbcategory. jspa?categoryID=66.
[3] Google storage for developers.
http://code.google.com/apis/storage/docs/overview.html.
[4] Windows Azure Platform.
http://www.microsoft .com/windowsazure/windowsazure.
5] Victims of lost files out of luck.
http://news.cnet.com/Victims-of-lost-files-out-of-luck/
2100-1023_3-887849.htm1, Apr. 2002.
[6] H. Abu-Libdeh, L. Princehouse, and H. Weatherspoon. RACS: a case for
cloud storage diversity. In Proc. 1st ACM Symp. on Cloud Comp., 2010.
7] M. Ahamad, G. Neiger, J. E. Burns, P. Kohli, and P. Hutto. Causal
memory: Definitions, implementation and programming. Distributed
Computing, 9(1):37—49, 1995.
8] Amazon S3 Team. Amazon S3 availability event: July 20, 2008.
http://status.aws .amazon.com/s3-20080720.htm1, July 2008.
9] C. Beckmann. Google app engine: Information regarding 2 July 2009
outage. http: //groups .google.com/group/google-appengine/
browse_thread/thread/e9237fc7b0aa7df5/ba95ded980c8c179,
July 2009.
[10] N. Belaramani, M. Dahlin, L. Gao, A. Nayate, A. Venkataramani,
P. Yalagandula, and J. Zheng. PRACTI replication. In NSDI, 2006.
[11] A. Birrell, R. Levin, R. Needham, and M. Schroeder. Grapevine: An
Exercise in Distributed Computing. CACM, 25(4), 1982.
[12] C. Cachin, I. Keidar, and A. Shraer. Fail-Aware Untrusted Storage. In
DSN, 2009.
[13] C. Cachin, A. Shelat, and A. Shraer. Efficient fork-linearizable access to
untrusted shared memory. In PODC, 2007.
[14] M. Calore. Ma.gnolia suffers major data loss, site taken offline. In Wired,
Jan. 2009.
[15] M. Castro and B. Liskov. Practical Byzantine fault tolerance and proactive
recovery. ACM TOCS, 20(4), 2002.
[16] EF Chang, J. Dean, S. Ghemawat, W. Hsieh, D. Wallach, M. Burrows,
T. Chandra, A. Fikes, and R. Gruber. Bigtable: A distributed storage
system for structured data. In OSDI, 2006.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI’10) = 321
322
{17]
[18]
[19]
[20]
[21]
[22]
[23]
[24]
[25]
[26]
[27]
[28]
[29]
[30]
[31]
[32]
[33]
[34]
[35]
[36]
[37]
[38]
[39]
[40]
[41]
[42]
[43]
[44]
[45]
[46]
[47]
[48]
[49]
[50]
B.-G. Chun, P. Maniatis, S. Shenker, and J. Kubiatowicz. Attested
Append-Only Memory: Making Adversaries Stick to their Word. In SOSP,
2007.
CircleID. Survey: Cloud computing ‘no hype’, but fear of security and
control slowing adoption. http: //www.circleid.com/posts/
20090226_cloud_computing_hype_security/, Feb. 2009.
A. Clement, M. Kapritsos, S. Lee, Y. Wang, L. Alvisi, M. Dahlin, and
T. Riché. UpRight cluster services. In SOSP, 2009.
B. Cook. Seattle data center fire knocks out Bing Travel, other web sites.
http://www.techflash.com/seattle/2009/07/Seattle_data_
center_fire_knocks_out_Bing_ Travel_other_Web_sites_
49876777 .html, July 2009.
B. Cooper, R. Ramakrishnan, U. Srivastava, A. Silberstein, P. Bohannon,
H. Jacobsen, N. Puz, D. Weaver, and R. Yerneni. PNUTS: Yahoo!’s
Hosted Data Serving Platform. In VLDB, 2008.
G. DeCandia, D. Hastorun, M. Jampani, G. Kakulapati, A. Lakshman,
A. Pilchin, S. Sivasubramanian, P. Vosshall, and W. Vogels. Dynamo:
Amazon’s highly available key-value store. In SOSP, 2007.
M. Demmer, B. Du, and E. Brewer. TierStore: a distributed filesystem for
challenged networks in developing regions. In FAST, 2008.
A. J. Feldman, W. P. Zeller, M. J. Freedman, and E. W. Felten. SPORC:
Group collaboration using untrusted cloud resources. In OSDI, Oct. 2010.
M. Frigo and V. Luchangco. Computation-Centric Memory Models. In
SPAA, 1998.
S. Gilbert and N. Lynch. Brewer’s conjecture and the feasibility of
Consistent, Available, Partition-tolerant web services. In ACM SIGACT
News, 33(2), 2002.
Git: The fast version control system. http://git-scm.com/.
E.-J. Goh, H. Shacham, N. Modadugu, and D. Boneh. SiRiUS: Securing
Remote Untrusted Storage. In Network and Distributed System Security
(NDSS) Symposium. Internet Society (ISOC), 2003.
J. Gray and P. Shenoy. Rules of Thumb in Data Engineering. In Data
Engineering, pages 3-12, 2000.
R. Guerraoui, N. Knezevic, V. Quema, and M. Vukolic. The next 700 BFT
protocols. In Eurosys, 2010.
A. Haeberlen, P. Kouznetsov, and P. Druschel. PeerReview: Practical
accountability for distributed systems. In SOSP, 2007.
A. Haeberlen, A. Mislove, and P. Druschel. Glacier: Highly durable,
decentralized storage despite massive correlated failures. In NSDI, 2005S.
J. Hendricks, G. R. Ganger, and M. K. Reiter. Low-Overhead Byzantine
Fault-Tolerant Storage. In SOSP, 2007.
M. Kallahalla, E. Riedel, R. Swaminathan, Q. Wang, and K. Fu. Plutus:
Scalable secure file sharing on untrusted storage. In Conference on File
and Storage Technologies (FAST), 2003.
B. Kang. S2D2: A framework for scalable and secure optimistic
replication. PhD thesis, UC Berkeley, Oct. 2004.
D. Karger, E. Lehman, T. Leighton, R. Panigrahy, M. Levine, and
D. Lewin. Consistent Hashing and Random Trees: Distributed Caching
Protocols for Relieving Hot Spots on the World Wide Web. In STOC,
1997.
J. Kistler and M. Satyanarayanan. Disconnected Operation in the Coda
File System. ACM TOCS, 10(1):3-5, Feb. 1992.
R. Kotla, L. Alvisi, and M. Dahlin. SafeStore: A durable and practical
storage system. In USENIX Technical, 2007.
J. Kubiatowicz, D. Bindel, Y. Chen, S. Czerwinski, P. Eaton, D. Geels,
R. Gummadi, S. Rhea, H. Weatherspoon, W. Weimer, C. Wells, and
B. Zhao. OceanStore: An Architecture for Global-Scale Persistent
Storage. In ASPLOS, 2000.
L. Lamport. Time, clocks, and the ordering of events in a distributed
system. CACM, 21(7), July 1978.
L. Lamport, R. Shostack, and M. Pease. The Byzantine Generals Problem.
ACM TPLS, 4(3):382-401, 1982.
D. Levin, J. R. Douceur, J. R. Lorch, and T. Moscibroda. TrInc: small
trusted hardware for large distributed systems. In NSDI, 2009.
J. Li, M. Krohn, D. Maziéres, and D. Shasha. Secure untrusted data
repository (SUNDR). In OSDI, 2004.
J. Li and D. Maziéres. Beyond one-third faulty replicas in Byzantine fault
tolerant systems. In NSDI, 2007.
P. Mahajan, S. Setty, S. Lee, A. Clement, L. Alvisi, M. Dahlin, and
M. Walfish. Depot: Cloud storage with minimal trust (extended version).
Technical Report TR-10-33, UT Austin, Sept. 2010.
D. Malkhi and M. Reiter. Byzantine Quorum Systems. Distributed
Computing, 11(4):203-213, Oct. 1998.
P. Maniatis. Historic Integrity in Distributed Systems. PhD thesis,
Stanford, 2003.
D. Maziéres, M. Kaminsky, M. F. Kaashoek, and E. Witchel. Separating
key management from file system security. In SOSP, 1999.
Mercurial. http: //mercurial.selenic.com/.
R. Miller. FBI siezes servers at Dallas data center.
http: //www.datacenterknowledge.com/archives/2009/04/03/
fbi-seizes-servers-at-dallas-data-center/, Apr. 2009.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
51
52.
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
S. Nath, H. Yu, P. B. Gibbons, and S. Seshan. Subtleties in Tolerating
Correlated Failures in Wide-area Storage Systems. In NSDI, 2006.
E. Nightingale, K. Veeraraghavan, P. Chen, and J. Flinn. Rethink the sync.
ACM TOCS, 26(3), 2008.
D. Oppenheimer, A. Ganapathi, and D. A. Patterson. Why do Internet
services fail, and what can be done about it? In USITS, 2003.
A. Oprea and M. Reiter. On consistency of encrypted files. In DISC, 2006.
D. S. Parker, G. J. Popek, G. Rudisin, A. Stoughton, B. J. Walker,
E. Walton, J. M. Chow, S. Kiser, D. Edwards, and C. Kline. Detection of
Mutual Inconsistency in Distributed Systems. IEEE TSE, 9(3):240-247,
May 1983.
K. Petersen, M. J. Spreitzer, D. B. Terry, M. M. Theimer, and A. J.
Demers. Flexible Update Propagation for Weakly Consistent Replication.
In SOSP, 1997.
E. Pinheiro, W. Weber, and L. Barroso. Failure trends in a large disk drive
population. In FAST, Feb. 2007.
R. A. Popa, J. R. Lorch, D. Molnar, H. J. Wang, and L. Zhuang. Enabling
security in cloud storage SLAs with CloudProof. Technical Report
MSR-TR-2010-46, Microsoft Research, May 2010.
V. Prabhakaran, L. Bairavasundaram, N. Agrawal, H. Gunawi,
A. Arpaci-Dusseau, and R. Arpaci-Dusseau. IRON file systems. In SOSP,
2005.
V. Ramasubramanian, T. Rodeheffer, D. B. Terry, M. Walraed-Sullivan,
T. Wobber, C. C. Marshall, and A. Vahdat. Cimbiosys: A platform for
content-based partial replication. In NSDI, 2009.
P. Reiher, J. Heidemann, D. Ratner, G. Skinner, and G. Popek. Resolving
File Conflicts in the Ficus File System. In USENIX Summer, 1994.
M. Shah, M. Baker, J. Mogul, and R. Swaminathan. Auditing to keep
online storage services honest. In HotOS , 2007.
A. Shraer, C. Cachin, A. Cidon, I. Keidar, Y. Michalevsky, and D. Shaket.
Venus: Verification for untrusted cloud storage. In CCSW, Oct. 2010.
A. Singh, P. Fonseca, P. Kuznetsov, R. Rodrigues, and P. Maniatis. Zeno:
Eventually consistent Byzantine fault tolerance. In NSDI, Apr. 2009.
J. Strunk, G. Goodson, M. Scheinholtz, C. Soules, and G. Ganger.
Self-securing storage: protecting data in compromised systems. In OSDI,
2000.
D.B. Terry, A. J. Demers, K. Petersen, M. Spreitzer, M. Theimer, and
B. W. Welch. Session guarantees for weakly consistent replicated data. In
ICPDS, 1994.
D.B. Terry, M. M. Theimer, K. Petersen, A. J. Demers, M. J. Spreitzer,
and C. H. Hauser. Managing update conflicts in Bayou, a weakly
connected replicated storage system. In SOSP, 1995.
US Secret Service report on insider attacks.
http://www.sei.cmu.edu/about/press/insider-2005.htm1,
2005.
W. Vogels. Life is not a state-machine: The long road from research to
production. In PODC, 2006.
B. White, J. Lepreau, L. Stoller, R. Ricci, S. Guruprasad, M. Newbold,
M. Hibler, C. Barb, and A. Joglekar. An integrated experimental
environment for distributed systems and networks. In OSDI, Dec. 2002.
T. Wobber, T. L. Rodeheffer, and D. B. Terry. Policy-based access control
for weakly consistent replication. In EuroSys, 2010.
J. Yang, C. Sar, and D. Engler. EXPLODE: A lightweight, general system
for finding serious storage system errors. In OSDI, 2006.
A. Yip, B. Chen, and R. Morris. Pastwatch: A distributed version control
system. In NSDI, 2006.
A. Yumerefendi and J. Chase. Strong accountability for network storage.
ACM Transactions on Storage (TOS), 3(3), Oct. 2007.
USENIX Association
USENIX Association
Comet: An active distributed key-value store
Roxana Geambasu, Amit A. Levy, Tadayoshi Kohno,
Arvind Krishnamurthy, Henry M. Levy
University of Washington
Abstract
Distributed key-value storage systems are widely used in
corporations and across the Internet. Our research seeks to
greatly expand the application space for key-value storage sys-
tems through application-specific customization. We designed
and implemented Comet, an extensible, distributed key-value
store. Each Comet node stores a collection of active storage
objects (ASOs) that consist of a key, a value, and a set of han-
dlers. Comet handlers run as a result of timers or storage oper-
ations, such as get or put, allowing an ASO to take dynamic,
application-specific actions to customize its behavior. Handlers
are written in a simple sandboxed extension language, provid-
ing properties of safety and isolation.
We implemented a Comet prototype for the Vuze DHT, de-
ployed Comet nodes on Vuze from PlanetLab, and built and
evaluated over a dozen Comet applications. Our experience
demonstrates that simple, safe, and restricted extensibility can
significantly increase the power and range of applications that
can run on distributed active storage systems. This approach fa-
cilitates the sharing of a single storage system by applications
with diverse needs, allowing them to reap the consolidation ben-
efits inherent in today’s massive clouds.
1 Introduction
The last decade has seen the rise of distributed stor-
age systems built on loosely coupled collections of au-
tonomous computers. For example, Amazon’s S3 [3]
provides a key-value storage service for external Web
clients. Amazon’s Dynamo [17], Apache Cassandra [5],
and Project Voldemort [38] provide reliable and scalable
key-value stores for company-internal applications (for
Amazon, Facebook, and LinkedIn, respectively). On the
global Internet, DHTs provided by BitTorrent-based sys-
tems, such as Vuze [58] and uTorrent [56], store metadata
for millions of clients using peer-to-peer file-sharing ap-
plications. And finally, researchers have developed com-
plete file systems on top of untrusted clients in widely
distributed P2P environments [2, 14, 44].
Distributed storage systems offer many advantages
over their centralized counterparts. For example, a de-
centralized structure supports scalability; the lack of cen-
tralized management enhances automatic load balancing;
and the use of replication in a highly distributed environ-
ment can improve reliability and data availability. We
therefore expect Dynamo-like storage systems to become
commonplace as generic application infrastructures in the
future, both inside of the enterprise and as shared services
on the Internet.
A significant limitation of such systems for generic
application support, however, is that different applica-
tions have different needs. As an example, each Dynamo
application inside of Amazon runs its own Dynamo in-
stance [17], even though a single instance might be log-
ically better and more resource efficient. In our own
work on Vanish [25] — a security-oriented DHT applica-
tion — we needed to make application-specific parame-
ter and policy changes to Vuze (a million-node commer-
cial DHT) in order to harden it against attack. While
these changes were conceptually simple, e.g., modi-
fying the storage replication algorithm, deploying our
changes took months of work with Vuze’s DHT designer.
Other Vuze applications may wish to make their own
application-specific changes or enhancements, but doing
so is neither feasible nor supportable, and it doesn’t scale.
We believe that with the huge consolidation benefits of
shared cloud storage services, either inside or outside of
the enterprise, supporting specialization of storage ser-
vices can have high payoffs in the future.
This paper presents Comet, a next-generation, flexi-
ble, distributed storage system, which opens the world
of distributed storage to a new set of more complex stor-
age applications. In particular, Comet permits multiple
applications to share a single Comet instance, while en-
abling each application to change the behavior of its stor-
age elements to suit its own requirements. For example,
a storage element can make decisions based on its access
history, its current number of replicas, the time of day,
etc. Therefore Comet can easily support different stor-
age lifetimes, access methods, access control schemes, or
replication schemes for different storage-element types,
in a way that makes them easy to deploy and test. Using
Comet, we can also carry out interesting measurement-
based experiments from within the DHT.
Comet implements active storage objects (ASOs). An
active storage object consists of a key, an associated value
(an untyped blob), and optionally, a set of simple han-
dlers. An ASO’s handlers execute as a result of com-
mon storage events on the object (such as get and put)
or from timer events that its handlers request. As a result,
an ASO can modify its environment, monitor its execu-
tion, and make dynamic decisions about its state.
The design of an extensible system for this environ-
ment presents a set of interesting design questions. For
example, what features should the system provide for ap-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) = 323
324
plications and which can (and should) be left out? What
is the proper tradeoff between power and safety? How
can client nodes be confident that active storage objects
will not cause damage or interference? How can we pre-
vent the use of active storage objects to mount a DDoS at-
tack? And overall, how can we extend the storage system
without losing its principal characteristics? Our Comet
design considers these and other issues.
The remainder of this paper describes our goals, ar-
chitecture, experience, and evaluation of Comet. To pro-
vide concrete insight into Comet’s design and potential,
we implemented a Comet prototype and used it to cre-
ate and deploy a set of over a dozen Comet applications.
Our prototype leverages Vuze: each Comet instance is
an extended Vuze client that can execute Comet active
storage objects while also serving as a full participant
in the million-node Vuze DHT. Comet applications are
written in Lua — a common application-extension lan-
guage. We modified the Lua runtime to meet our iso-
lation and safety requirements, providing a safe sandbox
for handler execution. To test our applications we ran our
Comet clients from several hundred PlanetLab nodes and
measured their behavior. Overall, our experience demon-
strates that a highly restrictive but active distributed stor-
age system can provide significant power to simultane-
ously support applications with diverse storage needs.
2 Related Work
The concept of extensible systems has been widely ex-
plored in the past in several domains. Extensible operat-
ing systems have been proposed that support application-
specific needs [6, 46, 28]. Active networks allow code
to be downloaded along with network data and executed
within the network infrastructure (e.g., on routers) to ex-
tend network services [60, 54]. Active messages execute
a small amount of user code with each message recep-
tion [57]. Click explored the design of an extensible
router [30]. Database triggers allow applications to define
procedural code that is executed in response to database
operations [35].
In the context of storage systems, Watchdogs [7] ex-
tends the Unix file system, allowing a user-mode process
to interpose on file operations for specific files to change
access semantics. Several projects have proposed the
integration of CPUs and disks to create intelligent disk
storage systems that can provide on-board application-
specific functions, e.g., for decision support systems, data
mining, and image processing [29, 41, 1].
DHTs are increasingly used to support a variety of dis-
tributed applications, such as file-sharing, distributed re-
source tracking, end-system multicast, publish-subscribe
systems, distributed search engines, and even data-center
applications. Some of these systems (e.g., as CFS [14],
13 [52], and PAST [44]) can be implemented using the
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
traditional put/get interface, but many others (e.g., Mer-
cury [8], CoralCDN [21], Scribe [45], and Bayeux [64])
require customized interfaces and are implemented by
altering the underlying DHT mechanisms in significant
ways. Our work provides the ability to extend a DHT
without requiring a substantial investment of effort to
modify its implementation.
Deployed DHTs don’t currently offer good semantics
and security. However, people do know how to make
them consistent [32, 34] and harden then against at-
tacks [11, 16, 48, 26, 59]. The reason DHTs do not cur-
rently implement these techniques is that there has not yet
been a deployed application that truly needed strong se-
mantics and security. For example, the Vuze design per-
ceived many threats as irrelevant [23] and deployed few
defenses against them. However, after the new, more de-
manding Vanish application was proposed [25], the Vuze
DHT responded by embracing a variety of effective secu-
rity measures. In addition to enabling new applications
atop DHTs, we hope to drive the design of these systems
towards well-understood, yet unadopted levels of security
and consistency.
3 Goals
Comet is a distributed key-value storage system. Like
other such systems, a Comet storage object is a
<key,value> pair. Unlike previous systems, however,
Comet’s design facilitates extensible, active storage ob-
jects. A Comet application performing a put can there-
fore include, along with a key and value, a small set of
handlers for that object. The node receiving the put
stores the handlers along with the key and value, registers
the handlers for events that they specify, and executes the
handlers when their respective events occur.
Comet’s system goals are:
1. Flexibility. Comet should be easily customizable to
achieve our target functions described below.
2. Isolation and safety. A client node running Comet
should be protected from the execution of handlers
(e.g., an executing handler cannot corrupt the node or
use unlimited resources). Handlers should not be able
to mount messaging attacks on other nodes.
3. Performance. The performance of gets/puts ona
Comet ASO with null handlers should be the same
as on a non-active system, and execution of handlers
should have only negligible performance impact.
Isolation and safety are particularly important to our
architecture. While Comet can be used in different envi-
ronments, we designed it to enable wide-scale, outside-
the-firewall deployment on autonomous nodes, similar to
P2P systems and DHTs. Users downloading Comet must
trust it and have guarantees about its behavior. For this
reason, Comet enforces four important restrictions:
USENIX Association
USENIX Association
1. Limited knowledge: an ASO is not aware of other ob-
jects or resources stored on the same node and has no
direct way to learn about them.
2. Limited access: an object handler can manipulate
only its own value and cannot modify the values of
other objects on its storage node.
3. Limited communication: an active storage object can-
not send arbitrary messages over the network.
4. Limited resource consumption: an ASO’s resource
usage is strictly bounded, e.g., the system limits the
amount of computation and memory it can consume.
We are specifically not attempting to build a general-
purpose distributed programming system, such as Planet-
Lab [4, 36]; such a system would be unacceptable in our
target environment and inappropriate (and unnecessary)
for our needs. Rather, our goal is to support relatively
simple specializations or actions on simple storage ob-
jects. Even very simple specializations can provide a sig-
nificantly more powerful storage system that enables new
types of applications. We therefore take a lightweight and
limited approach. As examples, an ASO should be able
to perform the following functions:
e Statistics gathering. Collect statistics about its use,
e.g., by counting the number of gets and puts.
e Information tracking. Log information, such as a list
of IPs that performed get operations on its value or a
recent history of the values it stored.
e Time awareness. Take time-based actions, e.g., to
make periodic changes to its state or self-destruct af-
ter a timer has elapsed.
e Location awareness. Make location-based decisions,
e.g., choosing where to store based on nodes’ network
locations.
e Access control. Implement simple access control
policies on its own.
e Replication. Implement different replication policies.
e Storage system measurement. Provide insight into the
behavior of the distributed storage system as seen by
clients executing within the system itself.
As we shall see, the only long-term state available to a
handler is its object’s value; therefore, any logs, counts,
etc., must be stored as part of that value. However, an ac-
tive object can choose to report only a subset of its stored
value record on a get, or it can selectively report different
values to different callers based on call parameters.
The following sections describe Comet’s architecture.
In particular, we discuss the tradeoffs required to provide
flexibility while also achieving isolation and safety.
4 Architecture and Implementation
This section describes Comet’s active storage architec-
ture and prototype implementation. One could imagine
running Comet in various environments, e.g., an inside-
the-firewall corporate deployment or a distributed envi-
ronment with autonomous untrusted nodes. We focus our
current architecture and prototype on the latter.
4.1 Architecture
Figure 1(a) shows the high-level architecture of our
Comet distributed storage system. The Comet storage
system consists of three basic components. First is the
routing substrate (Figure 1(a) bottom), which imple-
ments the value/node mapping, allowing a client to find
nodes that store specific data items. In the case of a DHT,
for example, the routing substrate typically applies a hash
function to the key to compute the IDs of nodes that store
the associated value. However, other routing substrates
may locate values in other ways.
The second component is the key-value store, which
maintains a set of key-value pairs on each node. A key-
value storage system typically exports a simple get /put
interface. While existing storage systems store arbitrary,
untyped byte strings, the Comet storage system stores ac-
tive storage objects (ASOs). An ASO consists of a key
and its associated state (i.e., a value, stored as an untyped
byte string), along with optional code that operates on
that state. The code is structured as a set of handlers that
specify how the object behaves, i.e., how it modifies its
state when certain events occur. For example, an ASO’s
onGet handler is invoked whenever a remote client per-
forms a get operation to access an object. This handler
might perform some simple operation, such as increment-
ing a counter for the number of gets or appending the
client’s IP address to a log structure. The counter or the
log structure would be stored as part of the ASO’s state
that can be accessed by the handler.
The third architectural component is the active runtime
system. The runtime system handles ASO invocations
and provides the security policy and execution environ-
ment. An application running on a remote client specifies
the initial state and handlers for an ASO when initially
storing the object via a put operation. When a client per-
forms a get or a put, it can optionally request a cryp-
tographic checksum of the code associated with the tar-
get ASO. This can serve as an integrity check that the
client’s initial put is to a key with no associated ASO
and that subsequent operations are performed on ASOs
created by the application. In most implementations, a
Comet node distrusts remote nodes and client applica-
tions; therefore, the runtime component of the active sub-
system implements and enforces an ASO execution sand-
box (Figure 1(a), top). Our Comet prototype uses a lan-
guage sandbox based on Lua [43] to prevent a handler
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) = 325
326
Storage Node
Bg ae
8 ASO,
os iss 2s
- state Application / ,
& - cade (handlers User getSystemTime() — UTC
getIP() — node’s external IP
getID(Q) — node’s DHT ID
getKey() > ASO’s key
deleteSelf(): deallocate ASO
(a) Architecture.
Figure 1: Comet Architecture and APIs.
ASO API ASO Handlers
L Remote
External | ,| Security |_| ASO Storage Node
Interaction Policies Runtime
Active Subsystem
K, | ASO, put/get/delete
kK, [ ASO,
Key-Value Store
¥
7 5 4
Routing Substrate
get(key, [args]) — value, nodes storing copies
put(key, value[, nodes])
lookup(key) — nodes closest to a key
(b) ASO API.
(a) depicts the decomposition of a Comet node into two vertical components - the
core Comet code, which is trusted from the node’s perspective, and the ASO code which is arbitrary and, therefore, untrusted. (b)
details the API exposed to ASOs.
from accessing outside state and to constrain the ASO
from consuming too many computational and memory re-
sources on the host. The ASO runtime consults a security
policy module, which specifies all execution limits.
While some applications may be satisfied by an en-
tirely sandboxed execution, many would benefit from an
ASO’s limited ability to interact with or “sense” its en-
vironment. For example, to implement the conditional
replication scheme we added to Vuze for Vanish, an ASO
requires knowledge of the number of replicas in the DHT
and the time of day (to enforce the desired minimum
replication interval). For this reason, the active subsys-
tem exposes a small API (called the ASO API) to the
handlers.
4.2 Active Storage Object API
Table 1 and Figure 1(b) show the handler and ASO run-
time APIs, respectively. The handler API supports invo-
cations based on the primary storage functions — put, get
— as well as an onTimer handler to be executed period-
ically (e.g., once every 10 minutes) during the object’s
lifetime. For example, an ASO could directly implement
a custom replication policy in its onTimer handler.
The ASO runtime API is the only way for an ASO
to interact with its environment outside of the sandbox.
Our design supports two types of useful interactions: (1)
obtaining information about the local node, and (2) ex-
ecuting various storage system operations. The former
category includes functions to obtain the time of day, the
hosting machine’s external IP address, etc. The latter in-
cludes functions to interact with other storage system ob-
jects. The ASO API was not designed to be entirely gen-
eral; rather, our goal was to provide a minimal interface,
informed in part by our requirements of security, privacy,
and isolation. We tested this interface by implementing
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
and running over a dozen applications on our Comet pro-
totype. Interestingly, we were able to build a relatively
diverse set of applications with a surprisingly small in-
terface, which has remained relatively stable through the
project. This suggests that a small interface, like the one
shown in Figure 1(b), can support a wide variety of appli-
cations. Naturally, there are limitations. For example, we
explicitly prohibit any direct network-level interactions
with remote nodes on the Internet. While this feature
might be desirable to certain measurement applications,
its DDoS implications would be unacceptable.
onGet(caller[, callbackID, payload])
Invoked when a get is performed on the ASO. Returns a value which will
be passed back to the caller. Instead of returning a value immediately, the
handler could also perform a put at the optional callbackID sometime in the
future. The handler also takes an optional payload argument of arbitrary
type.
onPut(caller)
Invoked upon initial put when the object is created. Returns the value that
should be stored by the node (e.g., itself or nil).
onUpdate(new_value, caller)
Invoked on an ASO when a put overwrites an existing value. Returns the
value that should be stored, e.g., new_value if it should be replaced, or itself
if not.
onTimer()
Invoked periodically. This handler has no return value. It is used to perform
periodic maintenance such as replication.
Table 1: ASO Handler Calls.
4.3 Language Based Sandbox
Our Comet prototype focuses on a DHT environment
composed of a large number of untrusted autonomous
nodes that cooperate to support the distributed active stor-
age system. In this environment, the key challenges in-
clude providing a strong sandbox and limiting ASO re-
source consumption. We briefly describe how our system
addresses these challenges using a language based sand-
box.
USENIX Association
USENIX Association
The Comet prototype required an ASO programming
environment that reflected our needs for simple extensi-
bility, flexibility, performance, isolation, and safety. To
meet these needs, we chose Lua [43], a lightweight and
easily constrained scripting language. A dynamically
typed, imperative and functional programming language,
Lua is most commonly used for coding application ex-
tensions. In this context, it lets users add or modify fea-
tures in video game engines, Web servers, version control
systems and other applications (specific examples include
World of Warcraft, SimCity 4, Adobe Photoshop Light-
room, and Squeezebox Jive Platform). Several properties
make Lua well suited for implementing ASOs. First, it
employes a small set of programming constructs (includ-
ing first-order functions) and a small number of data types
(including tables, which are heterogeneous associative ar-
rays). Second, Lua compiles to simple bytecode, which
makes it relatively easy to sandbox. Finally, ASOs writ-
ten in Lua are concise and small when serialized; the Lua
ASOs we implemented are all under 1.5KB, about five to
ten times smaller than Java equivalents.
Comet represents ASOs as Lua tables that encapsulate
both persistent state and the handlers to be invoked on
that state. Lua tables can implement basic arrays, asso-
ciative arrays, or both. While an associative array can
contain any name-value mappings, we treat certain asso-
ciations as handlers. In particular, if the ASO table con-
tains an associative array with the names “onGet,’ “on-
Put,” “onUpdate,’ or “onTimer” — and those names are
associated with values that are Lua functions — then the
runtime invokes those functions when the corresponding
events occur. Our runtime system serializes Lua tables
into a byte stream for transmission to a storage node on a
put request.
We made several modifications to the standard Lua in-
terpreter for the Comet runtime system. We sandbox
ASOs by removing all but the core libraries from the
runtime, leaving only a math package, string manipula-
tion, and table manipulation. As a result, handlers are
extremely restricted: they have no direct network access,
no system execution capabilities, no thread creation capa-
bilities, and no file system access. We also strictly bound
the amount of resources that a handler can consume. For
example, the runtime limits both the number of bytecode
instructions that a handler can execute and the amount of
memory it can consume. If a handler exceeds either of
these limits, the runtime terminates its execution.
The Comet runtime exposes a DHT wrapper object to
handlers, which allows an ASO to communicate with its
environment. The ASO can learn information about the
hosting node, including the external IP address and the
current system time. It can also perform a restricted set
of DHT operations. For example, it can perform get and
put operations on replicated copies of its value stored at
other nodes. In the API presented in Section 4.2, these
operations return values or neighboring node IDs. How-
ever, since these operation are slow in the DHT setting
and may block for seconds or even minutes, we chose to
implement them using function callbacks. Each such op-
eration takes an optional parameter, a function which ac-
cepts the result as its parameter. For example, instead of
returning a value, a get operation takes a function which
is eventually passed the result of the operation. The op-
eration returns immediately with no value, and the get
is actually performed after the ASO execution has com-
pleted. While this presents a slightly different paradigm
to the user, we think this provides a greater ability to op-
timize the performance of Comet-based applications.
4.4 Comet Prototype Implementation
We built the Comet prototype on the Vuze DHT, which
supports the widely used Vuze BitTorrent client. The
DHT is used mainly for distributed tracking of torrents;
however it has been used in research as well [27, 25].
Vuze implements the Kademlia routing protocol, in
which each node is assigned a 160-bit ID based on the
SHALL hash of its IP address and port. Basic DHT opera-
tions (get, put, and remove) take a 160-bit key, perform
a lookup to find nodes whose ID is close to that key, and
then send a read or store RPC to those nodes.
We minimally extended the Vuze interface to conform
to Comet’s abstract operations. For example, we aug-
mented get to allow a caller to pass an arbitrary byte-
string argument. This supports a parameterized get op-
eration, where the ASO can return different values de-
pending on the parameter (analogous to the semantics for
GET in HTTP).
Allowing extensibility in a DHT environment creates
challenges, e.g., it has the potential to provide a platform
for DDoS attacks. Therefore, in addition to the Lua re-
source restrictions described previously, we limit DHT
communications that ASOs can perform in two ways.
First, we do not allow an ASO to perform operations on
arbitrary DHT keys or nodes, but rather only on specific
key-node pairs. An ASO may communicate with any of
its neighboring nodes that are responsible for replicas of
the ASO. We also allow the ASO to communicate with
key-node pairs that have interacted with it in the past,
once for each such interaction. To enable this function-
ality, we extended Comet requests to include the ID of
the requesting node and the ID of a local key contained
within the node. If an ASO receives a get request with
a key ID specified, it gains the capability for a one-time
operation on that key to the node that issued the request.
The ASO can then either return a value immediately and
exhaust its one-time capability, or save that capability
for future use. This mechanism allows applications to
respond to DHT requests at a future point in time, es-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) 327
328
pecially if the requested data is not currently available.
We do not allow ASOs to pass these capabilities between
each other as doing so would enable a malicious node
to mount DDoS attacks. In Section 5 we discuss signed
ASOs, which do not have these restrictions.
Second, Comet imposes rate limits on the number of
messages generated by an ASO, either to neighboring
nodes storing replicas or to arbitrary key-node pairs that
have interacted with it in the past. This prevents misbe-
having ASOs from exhausting the bandwidth resources of
the Comet nodes hosting them. We discuss these security
issues further in Section 7.
5 Applications
This section seeks to demonstrate both the range of stor-
age behaviors that Comet can support and the ease with
which those behaviors can be implemented. To do this,
we describe several of the active storage applications
we have implemented, deployed, and measured on our
Comet PlanetLab prototype. We provide code snippets to
show how simply these actions can be programmed in our
Lua-based ASO environment. In Section 6, we present
measurements from some of these examples.
5.1 Customizable Replication
Most DHTs specify a fixed replication policy for stored
values, requiring applications to conform to that pol-
icy. In contrast, Comet ASOs can provide their own
application-specific replication mechanisms, e.g., con-
trolling the replication factor, the replication interval, and
the choice of nodes on which the object will be repli-
cated. This flexibility is useful for applications that place
varying degrees of emphasis on performance, availabil-
ity, locality, and security. For instance, a security sensi-
tive application (such as Vanish) might use a small num-
ber of replicas and long replication intervals, limiting the
dispersion of its objects stored in the DHT. On the other
hand, an application that values availability might repli-
cate frequently to a large number of nodes.
Listing 1 shows how an ASO can define a customized
replication policy. In this example, the onTimer han-
dler wakes up periodically, invokes lookup to deter-
mine a list of nodes closest to the ASO’s key, executes
selectGoodNodes! to identify a subset of nodes that will
serve as replicas, and then stores a copy of itself on the
selected nodes using put. We have also implemented a
timer handler that replicates only when the number of ex-
isting replicas falls below a certain threshold; this lowers
communication overhead and mitigates data harvesting
attacks for security sensitive applications, reflecting the
changes we made to Vuze after we published Vanish [25].
The Lua code for selectGoodNodes is omitted for brevity. It im-
plements an application-specific policy for choosing replicas.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
function aso:handleLookup(nodes)
nodes = self.selectGoodNodes(nodes)
dht.put(dht.getKey(), self, nodes)
end
function aso:onTimer()
dht.lookup(dht.getKey(), self-handleLookup)
end
Listing 1: Smart Replication
5.2 Controlling Data Access
Comet objects can implement various policies that con-
trol how data stored in the objects is accessed. We illus-
trate a few such examples.
Timeouts and Limited-read values: ASOs can be
used to implement objects that will be accessible for only
a limited, application-specified time. Such objects are
meaningful for security applications such as Vanish [25],
which provide support for self-destructing digital data by
storing cryptographic keys in a DHT.
Listing 2 shows the handler code required to imple-
ment application-specific timeouts. Each replica stores a
timestamp when the object is created (stored) and then
deletes the object after 60 minutes using a timer handler.
In addition, the onGet handler prevents the object’s con-
tents from being accessed after the timeout but before it
is deleted by a timer handler.
function aso:onPut(value)
self.timeout = dht.getSystemTime() + 60*MINUTES
return self
end
function aso:onTimer()
if (dht.getSystemTime() > self.timeout) then
—— delete local ASO
dht.deleteSelf()
end
end
function aso:onGet()
if (dht.getSystemTime() > self.timeout) then
—— delete local ASO
dht.deleteSelf()
return nil
end
return self
end
Listing 2: Timeouts
An ASO can also choose to delete itself after it has
been read — providing a “limited-read value” — where
each replica can be read at most once. In addition to
its use for self-destructing data, limited-read values could
be used in settings where objects represent tasks and are
deleted once they have been claimed by worker nodes.
The object then serves as a synchronizing construct be-
tween the task’s producer and consumer.
USENIX Association
USENIX Association
Listing 3 implements limited-read values. When a get
is performed, the node records the fact that the value has
been read. It then propagates the request to every other
replica by overwriting them with nil. Note that the ob-
ject does not delete itself immediately, but rather stays
around for a while and periodically attempts to delete
other replicas to ensure that copies on nodes with tran-
sient connectivity issues [22] are eventually deleted. Note
also that concurrent gets issued to different replica nodes
might successfully read the value. In general, as with
other distributed storage systems, consistent update of
replicated values would require the use of heavy-weight
consensus operations. Comet does not currently provide
such primitives. ASO handlers do however provide the
ability for replicas to detect and correct inconsistencies,
e.g., ASOs can compare and reconcile replica contents
through periodic invocations of the onTimer handler.
function aso:onGet()
if (self.read) then return nil end
self.read = dht.getSystemTime() + 30* MINUTES
dht.put(dht.getKey(), nil) ——deletes replicas
return self
end
function aso:onTimer()
if (self.read) then
dht.put(dht.getKey(), nil) ——deletes replicas
if (dht.getSystemTime() > self.read) then
dht.deleteSelf()
end
end
end
Listing 3: Limited-Read Values
Data Subscription: An ASO can allow clients to “sub-
scribe” so that they will be notified when the ASO re-
ceives a new value. In Listing 4, when the subscriber
performs a get, the ASO saves the subscriber’s network
location (callerNode) and a key that will serve as the
subscriber’s recipient of the value (callbackKey). When
a value update occurs, the ASO distributes the value to
all registered subscribers — the runtime ensures that the
ASO distributes these values only to clients who have
actually performed a get on the ASO. In the example
shown, the ASO clears its subscriber list after its put op-
erations; subscribers must then re-subscribe if they’re still
interested. Later we will describe an implementation of a
scalable publish-subscribe scheme based on this design.
Sensitive values: ASOs can implement various forms
of access control policies. For instance, Listing 5 pro-
vides read access to the object’s value only if the client
can present a predetermined password akin to a feature
already provided by some DHTs, like OpenDHT [40]. A
client provides the password as an argument to the get
aso.pending = {}
function aso:onGet(callerNode, callbackKey)
if(self. value) then
return self.value
end
self.pending[callerNode] = callbackKey
return nil
end
function aso:onUpdate(callerNode, value)
self.value = value
for callerNode,key in pairs(self.pending) do
dht.put(key, value, {callerNode})
end
self.pending = {}
end
Listing 4: Pub-sub
request.
There are a few issues with the code provided above,
especially if it were to be extended to support password-
protected updates. A malicious node could claim to store
the object but simply serve as a proxy for clients’ requests
and thereby implement man-in-the-middle attacks. This
could be solved by exposing basic encryption primitives
to the ASO, like a secure hash function and/or public key
cryptographic primitives. For example, instead of passing
the plaintext password to the ASO, the client hashes the
concatenation of the password with its IP/port, thus the
ASO can verify that the request is not being forwarded
by a malicious node. The ASO’s security can be further
strengthened by public/private key pairs, with the ASO
storing the public key and clients authenticating them-
selves by presenting a message signed with the corre-
sponding private key. With these enhancements, a ma-
licious node storing a copy of the object cannot overwrite
the contents of other replicas since it doesn’t possess the
private key.
function aso:onGet(caller, callerld, password)
if (password == ‘‘mypass1234’’) then
return ‘‘Well kept secret’’
end
return nil
end
Listing 5: Password
An application could use multiple mechanisms for
controlling data access, e.g., it could use timeouts in con-
junction with password-protected access. While Comet
does not allow ASOs to register multiple handlers for a
given storage operation, the developer can combine all of
the desired mechanisms into a single handler. Though
this might increase programming complexity, it allows
the application developer to control how different mech-
anisms interact with each other and provides the basis for
a predictable and deterministic execution model.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) 329
330
5.3. Measurements and Monitoring
DHT Measurements: ASOs provide a platform for
instrumenting and measuring the DHT using the DHT
nodes themselves. This enables a more detailed and com-
prehensive view of the DHT and helps provide accurate
estimates of DHT properties such as churn, node lifetime
distribution, transient inconsistencies, etc.
For instance, Listing 6 tracks the k closest nodes to the
ASO and stores the information it learns as part of the
object state. A measurement application can create ob-
jects of this type, store them at multiple locations within
the DHT, and obtain snapshots of DHT membership by
retrieving the objects’ contents using get operations.
aso.neighbors = {}
function aso:handleLookup(nodes)
self neighbors[dht.getS ystemTime()] = nodes
end
function aso:onTimer()
dht.lookup(dht.getKey(), self-handleLookup)
end
Listing 6: Lifetime
While this measurement could be performed by nodes
that are not part of the DHT (as in earlier work [20, 50]),
measurements from within the DHT can provide more ac-
curate data. For example, the lifetime measurement could
be carried out by a client that interactively crawls the
routing tables of the DHT nodes and then uses heartbeat
messages to monitor the uptimes of the nodes it learns
about. This approach could provide faulty data, however,
if the DHT contains firewalled nodes that do not receive
or respond to such heartbeat messages.” On the other
hand, firewalled nodes still communicate with neighbors,
for example to replicate values. Therefore, measurements
performed from ASOs within the DHT can be more ac-
curate, as we will demonstrate later.
Monitoring uses: An ASO can also maintain audit
trails, e.g., indicating where it has been stored thus far,
who has read or updated the object, etc. Such tasks are
particularly useful for debugging and aid in rapid proto-
typing. For example, this may help a developer to learn
whether a new ASO replication mechanism is operating
properly. Alternately, logs can also be used for forensics.
Listing 7 illustrates a monitoring application that tracks
the nodes storing and accessing a value.
This specific implementation comes with a few
caveats. Each replica may have a different view of the
list of nodes that have stored or read the value. To address
this, the experimenter needs to get the union of the lists
stored in all the replicas, consolidating them as a post-
processing step.
21n fact, about half the nodes in P2P DHTs are firewalled [23].
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
replicalps, hostIps, accessorIps = {}
function aso:onGet(callerIp)
table.insert(self.accessorIps, callerIp)
return self
end
function aso:onPut(caller)
table.insert(self.accessorIps, caller.getIP())
table.insert(self-hostIps, dht.localNode.getIP()
return self
end
function aso:handlePut(nodes)
for i,v in ipairs(nodes) do
table.insert(self.replicalps, v.getIp()
end
end
function aso:onTimer()
dht.put(dht.getKey(), self, 20, self.handlePut)
end
Listing 7: Monitoring
5.4 Smart Rendezvous
DHTs are used for rendezvous in many distributed sys-
tems. In P2P file-sharing systems such as BitTorrent, the
DHT is used as a distributed tracker either with or as a
replacement for a centralized tracker. That is, peers that
want to download a particular file use the DHT to iden-
tify other peers who are downloading or sharing the file.
The downside with current DHT-based distributed track-
ers, however, is that they result in random overlay con-
nections, as there is no mechanism to enforce more intel-
ligent peer-matching techniques.
With Comet we can address this limitation by using
ASOs to track participating nodes, as well as construct
peer lists that are optimized for a requesting node. Peers
could be matched in order to lower inter-node laten-
cies [33], maximize reciprocation probability based on
peer bandwidths [37], or lower ISP costs [62, 12]. We
have implemented one such matching scheme that uses
the nodes’ network coordinates to predict inter-node la-
tencies and provides a list of nearby peers to each joining
node. We describe this in depth in Section 6.3.2.
5.5 Signed ASOs
The examples discussed so far adhere to the strict security
policy we set out: ASOs cannot perform operations on ar-
bitrary DHT keys or nodes. We now consider uses where
we relax this assumption, but require that the ASO code
be signed by the DHT administrator after manual verifi-
cation of its security properties. As we will see below,
this allows the DHT to deploy new functionality and ser-
vices by using signed ASOs that access arbitrary DHT lo-
cations, but are safe (i.e., they do not enable DoS attacks
of targeted DHT nodes).? We have considered signed
3In some cases, the safety of the ASO code could presumably be ver-
ified automatically, e.g., by using sophisticated compile-time analysis;
USENIX Association
USENIX Association
ASOs in particular as a mechanism that a DHT’s devel-
oper or administrator could use for testing and evaluation
of new features, before they are added to the main-line
DHT code.
Recursive Get: Vuze and many other DHTs support
iterative routing for key lookups. In this approach, the
node performing the lookup is involved in every step of
the routing operation, i.e., it identifies the target node by
repeatedly querying DHT nodes to find other nodes that
are closer to the target key. An alternative is to perform
recursive routing, where intermediate nodes on the route
pass the lookup directly to nodes that are closer to the
key. Iterative lookup provides greater control to the node
performing the lookup (e.g., it can control lookup paral-
lelism), but it comes at the cost of increased latency. If
both forms of lookup are available, an application would
use recursive lookups by default, but fall back on iterative
lookups after persistent failures [15].
With signed ASOs it is possible to implement recursive
lookups even though the underlying DHT supports itera-
tive lookup by default (as is the case with Chord, Kadem-
lia, and Vuze). The node initiating the lookup creates a
query ASO, which contains a reference to itself, and a lo-
cal callback ID where it would like to receive the answer.
When the signed ASO is created its onPut handler is in-
voked; the handler queries the local routing table to find
a live node that is closest to the target key, stores a copy
of the signed ASO on this node, and deletes itself from
the current node. This process is repeated until one of
the nodes storing the target is reached, and the onUpdate
handler of the target ASO sends the object’s value back
to the original node, which initiated the request.
Caching and Hierarchical Publish-Subscribe: This
idea can be extended to accomplish both caching and hi-
erarchical publish-subscribe data delivery. For caching,
the onUpdate handler can be modified to communicate
the object not only to the requesting node but also to the
intermediate node that conveyed the request. The number
of intermediate nodes to which the object is replicated can
be determined by gathering and analyzing statistics on
object popularity (also accomplished using simple han-
dler code), so that only popular objects are replicated at
multiple nodes (as in Beehive [39]). To implement hierar-
chical publish-subscribe, intermediate nodes propagate a
subscription event to the next node in the lookup process
only if they haven’t done so before and maintain state
for subsequent queries routed to them. When a value is
published, it is propagated through a dissemination tree
so that the communication load is distributed across all
intermediate nodes (as in Scribe and Bayeux [45, 64]).
studying this is part of future work.
5.6 Summary
This section described a set of example storage objects
that we have implemented using Comet. Through these
examples, it should be clear that with very small exten-
sions (on the order of a few lines or a few tens of lines
of code), a Comet application can create a wide range of
powerful storage object behaviors that would be impossi-
ble in existing distributed storage systems or DHTs.
6 Evaluation
We deployed Comet on approximately 200 PlanetLab
hosts and evaluated our design in three steps. First, we
characterize the resource utilization of the various appli-
cations that we developed. Second, we measured micro-
benchmarks to understand the overheads associated with
active storage objects. Lastly we report on our experi-
ences with prototyping applications using Comet.
6.1 Application Characteristics
Table 2 shows resource consumption requirements for
our Comet applications. The Max Instructions column
gives the number of dynamic Lua instructions required
to execute the most expensive handler, while Execution
Time gives the execution time for that handler. Where
this value is data sensitive, we provide an estimate based
on the expected maximum value. Code Size shows the
size of each ASO with the minimum amount of data and
Max Size is the maximum size to which the ASOs might
grow for that application. From the table we see that most
ASOs execute fewer than 100 Lua instructions and are
smaller than 1KB in size.
Application Max Execution] Code Max
Instructions| Time Size Size
| Replication <10 4us 0.223K | <1K |
| Smart Replication < 100 6us 0.444K | <1K |
| Timeouts ~ 10 4us 0.434K | <1K |
| Limited-Read Value = 10 4us 0.553K | <1K |
| Sensitive Value < 10 4us 0.230K | <1K |
Pub Sub 10, 000s 54us 0.498K | 100K
| Hierarchical Pub Sub | 100s 6us 0.673K | 1K |
| Lifetime (External) 100s 6us 1K 6K/hr |
| Lifetime (Internal) < 100 6us L716K | ~3K |
| Monitoring =~ 10 4us 0.971K | 3K/hr |
| Smart Rendezvous 1,000s 14us 1.107K | 10K |
| Recursive Get = 50 6us 0.714K | = 1K |
Table 2: Expected Application Resource Consumption
6.2. Performance and Overheads
We report on simple microbenchmark measurements to
compare the CPU and memory costs of Vuze and Comet.
These experiments were run on an quad-core machine
with Xeon processors clocked at 2.67GHz.
Single-Node Throughput. In this experiment, concur-
rent get operations are performed on many values stored
in the target node. We measure the throughput of get
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI’10) 331
332
1e+07
1e+06
100000
10000
1000
100
Vuze (Not active)
= Oinstructions 13¢e"
10 10K instructions
100K instructions
1Minstructions ««
Log(Throughput) (operations per second)
oO 20 40 60 80
Offered load (concurrent operations)
100
(a) Single-Node Throughput.
800
Comet - Null values —-+-—
700 Vuze - Null values +34.
600
500
400
300
Memory consuption in MB
200
OF eel
sense MG eeeeneseneseeeenesDAQUEPRFFITRE Seer
10 100 1000 10000 100000
Log(Number of values stored)
1e+06
(b) Memory Footprint.
Figure 2: Microbenchmarks.
requests that return successfully using a closed feedback
loop. All operations are issued locally on the node, so
that network latency does not affect throughput.
Figure 2(a) compares the throughput of objects with
different ASO execution costs, expressed as the number
of Lua bytecode instructions executed per handler. Both
Comet and Vuze experience peak throughput when the
number of concurrent operations is equal to the num-
ber of cores (eight). ASOs with zero instructions per
handler are functionally equivalent to Vuze values as
they simply return themselves. The peak throughput of
Comet ASOs is about 60% smaller than the peak through-
put of Vuze (1.4M operations per second as opposed to
3.5M operations per second). This shows the cost of
the Comet/Lua execution environment. Previous mea-
surements [49] show that the typical DHT load on Vuze
clients in the wild is at most a few hundred operations
per second, which makes the additional Comet overhead
relatively insignificant in this context. As we increase
the computational complexity of the average ASO (1K to
1M instructions per handler), the throughput decreases,
but still remains well above the maximum current Vuze
workload.
Operation Latency. At the 90th percentile, with maxi-
mum throughput (8 concurrent operations in our exper-
iments), a request involving 100 Lua instructions has a
latency of about 300 microseconds. For handlers with
1M instructions (two orders of magnitude more than our
most compute-intensive handlers), it is 13 milliseconds.
The latency for a Vuze DHT lookup is on the order of
seconds, therefore the latency imposed by even extremely
computationally intensive ASOs is not significant.
Memory Footprint. In this experiment, we store in-
creasing numbers of values in the nodes. For the Vuze
nodes, the string “hello world” is stored at different keys,
while for Comet nodes we store an equivalent Lua ASO
which returns the same string upon a get request. Fig-
ure 2(b) compares the memory footprint of the Vuze and
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
Comet nodes as we increase the number of stored ob-
jects. Again using the median number of values stored
per Vuze node (around 400), the difference in memory
consumption at this level is negligible (about 36MB for
both Comet and Vuze). Long lived DHT nodes can store
10,000s of values, and the highest observed is around
30,000 values [49]. In these rare cases, our overhead rela-
tive to Vuze is about 27%, but even then the total memory
footprint is still reasonable.
We next consider a workload where Comet object
sizes are exponentially distributed with an average size
of 1OKB. In this case, a node with 5OOMB can store on
average 50,000 values. If we assume an order of mag-
nitude more values per node than in Vuze (4,000 instead
of 400), and an order of magnitude larger values (LOKB
instead of 1KB limit imposed by Vuze), the median node
would consume about 80MB (40MB of startup memory
costs and another 40MB for the ASOs) in memory.*
6.3 Application Experience
We now report on our experiences in prototyping and de-
ploying some of the applications described in Section 5.
6.3.1 Measuring Node Lifetimes
We revisit the experiment performed by Falkner et al. [20]
to measure the lifetimes of nodes in the Vuze DHT. This
experiment was done by performing random get opera-
tions from several Vuze clients in order to gather approxi-
mately 300K IPs participating in the DHT. The collection
of nodes was then pinged every 2.5 minutes to check for
liveness. The authors observed that nearly half the nodes
were immediately unavailable after first being detected.
One weakness of the methodology employed is that the
clients could not differentiate nodes that are unreachable
because of NATs from those that have left the DHT. Us-
ing measurement nodes that have active communication
channels with NATed DHT nodes would help minimize
4Vuze and Comet consume about 40MB without storing any values.
USENIX Association
USENIX Association
Cumulative fraction
0.1 +
External Measurement —+—
__Internal Measurement -
oO 5 10 15 20 25 30
Duration in DHT since first observation
Figure 3: Node Lifetimes in Vuze.
measurement bias, but would require the measurement to
be performed by nodes that are within the DHT.
Comet enables researchers to deploy experiments us-
ing measurement ASOs executed on nodes that are part
of the DHT. To demonstrate the feasibility of this ap-
proach, we deployed Comet on 190 geographically dis-
persed PlanetLab nodes and integrated them into the pro-
duction Vuze DHT. The measurement ASOs are stored
on the Comet nodes, and they gather information about
unmodified Vuze nodes that are adjacent (in the DHT)
to the Comet nodes. We stored a lifetime measurement
ASO (a variant of the code shown in Listing 6) at each of
the Comet nodes, allowed the nodes to perform measure-
ments for several days, and then collected and analyzed
the data from these nodes.° Figure 3 plots the measure-
ment data obtained from our experiments and compared
to the lifetime data obtained by measurement nodes that
are not integrated into the DHT (as in [20]). We observe
that the measurements performed from within the DHT
provide higher estimates for node lifetimes. The reason
is that DHT-internal measurement nodes are able to tra-
verse NATs in communicating with their neighbors. The
difference is significant; we measured the median node
lifetime as 3.1 hours, as opposed to an estimate of 0.5
hours obtained through conventional external measure-
ments. Measurement ASOs are thus valuable tools in
characterizing DHTs and provide more accurate data for
tuning parameters such as replication factor, routing par-
allelism, etc.
6.3.2 Smart Rendezvous
In Section 5, we proposed a way to employ intelligent
peer tracking for distributed P2P trackers using ASOs.
We evaluate the usefulness of this application by deploy-
ing a distributed tracker built with Comet ASOs. As with
traditional distributed trackers, clients participating in a
P2P swarm (such as a BitTorrent download) register their
5As Comet is not currently deployed by Vuze, the measurement
ASOs are stored only on the nodes that we control. A more extensive
deployment would allow us to obtain more samples quickly.
Cumulative fraction
0.1 Vivaldi —+— |
__ Random
50 100 150 200 250
Latency between paired nodes (ms)
Figure 4: Proximity of BitTorrent peers.
participation by storing their IP addresses under the ap-
propriate DHT key. In addition, clients also store their
network coordinates (computed using Vivaldi [13]) along
with their IP information. When clients contact the dis-
tributed tracker to obtain peer lists, the tracker ASO esti-
mates the network latency between pairs of nodes using
the supplied network coordinates and returns peers that
are likely to be close to the requesting node. To evaluate
this approach in practice, we deployed a tracker ASO on
a Comet node in PlanetLab, while 190 PlanetLab nodes
acted as peers in the swarm reporting their Vivaldi coordi-
nates to the tracker and requesting good peers with which
to communicate. Figure 4 depicts the effectiveness of this
strategy compared to the default strategy of returning a
random subset of peers to the requesting node. The graph
shows a CDF of the measured latencies between peers
under the two different matching schemes. The median
value for the ASO-implemented Vivaldi intelligent peer
matching is 47ms compared to a median of 72ms for the
default scheme, a 35% latency improvement.
6.3.3 Vanish
Comet grew in part out of our experience specializing the
Vuze DHT for Vanish [25], a self-destructing data sys-
tem. Vanish used Vuze for key storage, however, Wol-
chock et al. [61] showed that the Vuze system was ex-
tremely open to a Sybil data harvesting attack that is able
to scan the DHT for values. The attack worked in part
because of Vuze’s overly zealous replication policy — a
high replication factor, coupled with a policy to repli-
cate to new nodes immediately. In response, we set out
to deploy new replication mechanisms and other anti-
Sybil defenses in Vuze [24]. While these mechanisms
were straightforward, deploying them required the co-
operation of Vuze’s designer and was an arduous and im-
perfect process. While many iterations would have been
necessary to fully test and optimize policies, we often
had only one shot to catch the two-month release cycle.°
®It takes a week or more from release until 80% of the nodes in
Vuze adopt changes. This is in addition to a typical release cycle Vuze
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) = 333
334
For the same reason we were unable to test individual
changes in isolation as they had to be shipped in bundles
in order to make progress in reasonable time.
We have used Comet to re-implement several of the
changes that we deployed in Vuze. Those changes in-
clude the customizable replication scheme described in
Section 5 (particularly a scheme that replicates only when
the number of replicas falls below a threshold) and vari-
able object lifetimes. As we showed in Section 5, both
of these changes are trivial to program as Comet ASOs.
Perhaps even more important, testing and re-deployment
in Comet is significantly easier, as it does not require a
redistribution of the entire DHT code base. Instead, new
mechanisms can be deployed by overwriting the handler
code for existing objects and using the updated bytecode
for subsequently created objects, without requiring the in-
volvement of the DHT administrators.’ Had Comet ex-
isted at the time we deployed Vanish, it would have been
possible to customize the DHT for the security require-
ments of the application from the start, and to optimize
those policies to Vanish’s requirements.
7 Security Analysis
The classic security goals for DHTs include resilience to
attacks that: violate the system’s ability to robustly store
data [48], disrupt routing [48, 11], identify the partici-
pating nodes in the DHT [53, 51], and harvest copies of
data stored within the DHT [61]. There are numerous
well-known techniques aimed at violating these goals, in-
cluding Sybil attacks [19], Eclipse attacks [47], and many
others [55]. And there are also many known mechanisms
for protecting against such attacks, including the use of
strong identities minted by a logically centralized author-
ity, computational puzzles and bandwidth contributions
proofs [9, 16, 18, 63, 10], and architectures built upon so-
cial network structures [31, 63]. A production DHT with
ASO support must consider such classic security goals,
and can leverage known countermeasures for the corre-
sponding threats. (Although, as exemplified by Vuze and
other popular DHTs, a DHT for ASOs may decide that
the risks associated with these threats are minimal, and
hence not deploy the known defenses.)
The security concerns of DHTs with signed ASOs
are roughly those of conventional DHTs without ASOs
(since the signed ASOs can be viewed as “vetted” parts
of the DHT system itself); we therefore do not consider
signed ASOs further. Empowering DHTs with unsigned
ASOs does, however, create a new potential attack vec-
tor not present in conventional DHTs — namely, attacks
employs, which spans about a month.
7In general, updating the handler code for existing objects would
require the application to keep track of its ASOs. In the case of appli-
cations such as Vanish, where objects are transient and have timeouts
in the order of a few hours, we can also let existing objects just expire
without explicitly updating them.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
via malicious ASOs. We seek to ensure that a malicious
ASO cannot: infer private information about or damage
its Comet hosting node; infer information about or af-
fect the properties of other ASOs stored within Comet;
or infer private information about or affect the proper-
ties of other Comet nodes and arbitrary computers on
the Internet. To place these goals in context, we stress
that while an attacker could always use her own custom
software to communicate with Comet in arbitrary ways,
including putting to or getting from arbitrary ASO keys
and communicating with the broader Internet in arbitrary
ways, our goals — if attained — imply that ASOs cannot
be used to amplify the attacker’s resources or capabili-
ties. For example, an attacker should not be able to create
an ASO “worm” that spreads virally, mounting a DDoS
attack against a victim ASO or device on the Internet.
We find that it is possible to meet these goals using
three architectural features: (1) restricting system access,
(2) restricting resource consumption, and (3) restricting
within-Comet communication. We consider each in turn.
Restricting system access. We designed the ASO API
to be highly restrictive. The API explicitly restricts an
ASO’s ability to infer private information about its host
or to affect the host’s state. The API similarly restricts an
ASO’s ability to interact with arbitrary devices on the In-
ternet. For example, the API limits an ASO’s IO capabil-
ities to explicitly defined DHT operations; arbitrary disk,
network, and other IO operations are prohibited. The API
also prevents an ASO from introspecting its host; e.g., al-
though we allow the ASO to learn its host’s external IP,
we explicitly prevent the ASO from learning its host’s in-
ternal IP. Without these restrictions, an ASO could poten-
tially read private files on the host’s disk, write sensitive
files, attempt to DoS an arbitrary remote node, map the
network topology of internal IP networks, and so on. The
Lua sandbox provides a simple mechanism for achieving
this isolation. Namely, we removed the IO system call
interface and exposed one containing only the restricted
DHT operations instead.
Despite these restrictions, it may be possible for an
ASO to infer (minimal) information about the hosting
node via side-channels. For example, the time it takes
an ASO to perform a computation could leak informa-
tion to the ASO about the speed of the hosting processor.
At the extreme, it may be feasible to infer modest infor-
mation about other applications running on the hosting
node [42]. We believe that such attacks are low risk in
the Comet environment and do not consider them here.
Restricting resource consumption. Comet also signif-
icantly limits an ASO’s ability to consume resources on
its hosting node. Our prototype limits both the memory
and CPU consumption of ASOs.
Memory. The Comet active runtime keeps a running
USENIX Association
USENIX Association
sum of the memory footprint of an ASO. Hard limits can
be set on the total memory consumption of an object;
ASOs which exceed this limit are evicted. Our current
prototype limits ASOs to 100kB.
CPU. The Comet runtime similarly keeps a running
count of bytecode operations performed. We envision
multiple policies for constraining CPU use. The naive
policy limits each ASO to at most a limited number of
instructions per handler invocation. Since not all Lua op-
erations are equally costly, a more sophisticated policy
would assign different weights to different Lua opera-
tions (e.g., more cost for a table lookup than an addition).
The limit could also be enforced over a fixed duration of
time (such as 30 minutes) rather than upon each handler
invocation (which might occur much more frequently).
Our current prototype implements the naive restriction
and allows 100K instructions per handler invocation.
Comet provides support for exception handling in or-
der to help debug faulty ASOs that exceed the system-
imposed resource limits. Handlers can catch resource ex-
haustion exceptions and store the relevant handler state
as part of the ASO. The developer can then retrieve this
stored state and inspect it to determine why the handler
exceeded the resource limits. Further, operations that re-
turn values, e.g., gets, provide the stack trace as a return
value in the case of an exception. We found these fea-
tures to be useful in debugging many of the applications
that we prototyped using Comet.
Restricting within-Comet communications. There
are two classes of communications that we must consider:
communications between one ASO and another, and call-
back communications to a caller.
Communications between ASOs. Allowing arbitrary
between-ASO communications in Comet could lead to
abuse. For example, suppose a malicious ASO stored
under one key copies itself to a large number of other
keys slowly over time, and then simultaneously all ASOs
initiate connections to a victim ASO stored under some
target key. Such an attack allows an attacker to am-
plify her resources: the attacker invests minimal effort
to seed the original malicious ASO, yet the ultimate at-
tack DDoSes nodes hosting the target key. Comet takes
a Draconian approach toward protecting against such at-
tacks: the ASO API only allows ASOs to communicate
if they are stored under the same key, whether co-located
on the same Comet node or on another node within the
DHT. Our system further rate-limits communications per-
formed by a particular ASO. Each Comet node allots a
limited number of network communications per time pe-
riod for every ASO it hosts. Though we have not ex-
perimentally ascertained appropriate rate-limiting param-
eters, the applications we present could all work with ap-
proximately the same number of network operations as is
required for a value in the current Vuze DHT - about 20
every timer interval.
8 Conclusions
This paper described Comet, an active distributed key-
value store. Comet enables clients to customize a dis-
tributed storage system in application-specific ways us-
ing Comet’s active storage objects. By supporting ASOs,
Comet allows multiple applications with diverse require-
ments to share a common storage system. We imple-
mented Comet on the Vuze DHT using a severely re-
stricted Lua language sandbox for handler programming.
Our measurements and experience demonstrate that a
broad range of behaviors and customizations are possi-
ble in a safe, but active, storage environment.
9 Acknowledgements
This work was supported in part by the National Science
Foundation under grants NSF-0627367, NSF-0614975,
NSF-0619836, NSF-0722004, and NSF-0963754, by the
Google Fellowship in Cloud Computing, and by the
Wissner-Slivka Chair. We thank Paul Gardner for his
support on Vuze, and David Wetherall and our shepherd
Wilson Hsieh for their helpful feedback on the paper.
References
[1] A. Acharya, M. Uysal, and J. Saltz. Active disks: Programming
model, algorithms and evalaution. In Proc. of the 8th Conference
on Architectural Support for Programming Languages and Oper-
ating Systems, October 1998.
[2] A. Adya, W. Bolosky, M. Castro, G. Cermak, R. Chaiken,
J. Douceur, J. Howell, J. Lorch, M. Theimer, and R. Wattenhofer.
Farsite: Federated, available, and reliable storage for an incom-
pletely trusted environment. In Proc. of OSDI, 2002.
[3] Amazon S3. http://aws.amazon.com/s3/.
[4] T. Anderson, L. Peterson, S. Shenker, and J. Turner. Overcom-
ing the Internet impasse through virtualization. JEEE Computer,
38(4), April 2005.
[5] Apache Cassandra. http://cassandra.apache.org/.
[6] B. Bershad, S. Savage, P. Pardyak, E. G. Sirer, D. Becker, M. Fi-
uczynski, C. Chambers, and S. Eggers. Extensible, safety and
performance in the SPIN operating system. In Proc. of the 15th
ACM Symp. on Operating systems Principles, December 1995.
[7] B.N. Bershad and C. B. Pinkerton. Watchdogs — extending the
UNIX file system. Computer Systems, 1(2), 1988.
[8] A. R. Bharambe, M. Agrawal, and S. Seshan. Mercury: Support-
ing scalable multi-attribute range queries. In Proc. of SIGCOMM,
2004.
[9] N. Borisov. Computational puzzles as Sybil defenses. In Proc. of
the Intl. Conference on Peer-to-Peer Computing, 2006.
[10] N. Borisov. Computational puzzles as Sybil defenses. In Proc. of
the Intl. Conference on Peer-to-Peer Computing, 2006.
[11] M. Castro, P. Druschel, A. Ganesh, A. Rowstron, and D. S. Wal-
lach. Secure routing for structured peer-to-peer overlay networks.
SIGOPS Oper. Syst. Rev., 2002.
[12] D. R. Choffnes and F. E. Bustamante. Taming the Torrent: A
practical approach to reducing cross-ISP traffic in P2P systems.
In Proc. of SIGCOMM, 2008.
[13] F. Dabek, R. Cox, F. Kaashoek, and R. Morris. Vivaldi: a de-
centralized network coordinate system. In Proc. of SIGCOMM,
2004.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) 335
336
14]
15]
18]
F. Dabek, M. F. Kaashoek, D. Karger, R. Morris, and I. Stoica.
Wide-area cooperative storage with CFS. In Proc. of SOSP, 2001.
F. Dabek, J. Li, E. Sit, J. Robertson, M. F. Kaashoek, and R. Mor-
ris. Designing a dht for low latency and high throughput. In NSDI,
2004.
G. Danezis, C. Lesniewski-Laas, M. F. Kaashoek, and R. J. An-
derson. Sybil-resistant DHT routing. In ESORICS, 2005.
G. DeCandia, D. Hastorun, M. Jampani, G. Kakulapati, A. Laksh-
man, A. Pilchin, S. Sivasubramanian, P. Vosshall, and W. Vogels.
Dynamo: Amazon’s highly available key-value store. In Proc. of
SOSP, 2007.
J. Dinger and H. Hartenstein. Defending the Sybil Attack in
P2P Networks: Taxonomy, Challenges, and a Proposal for Self-
Registration. In Intl. Conf: on Availability, Reliability and Secu-
rity, 2006.
J. R. Douceur. The Sybil attack. In Proc. of IPTPS, 2002.
J. Falkner, M. Piatek, J. P. John, A. Krishnamurthy, and T. Ander-
son. Profiling a million user DHT. In Proc. of IMC, 2007.
] M. J. Freedman, E. Freudenthal, and D. Maziéres. Democratizing
content publication with coral. In NSDI, pages 239-252, 2004.
M. J. Freedman, K. Lakshminarayanan, S. Rhea, and I. Stoica.
Non-transitive connectivity and DHTs. In WORLDS’05, pages
10-10, Berkeley, CA, USA, 2005. USENIX Association.
P. Gardner. personal communication, 2009.
R. Geambasu, T. Kohno, A. Krishnamurthy, A. Levy, H. M. Levy,
P. Gardner, and V. Mascaritolo. Cascade: A compositional ap-
proach to self-destructing data. In Preparation, 2010.
R. Geambasu, T. Kohno, A. Levy, and H. Levy. Vanish: Increasing
data privacy with self-destructing data. In Proc. of the USENIX
Security Symposium, August 2009.
K. Hildrum and J. Kubiatowicz. Asymptotically Efficient Ap-
proaches to Fault-Tolerance in Peer-to-peer Networks. In Proc.
of International Symposium on Distributed Computing, 2004.
T. Isdal, M. Piatek, A. Krishnamurthy, and T. Anderson. Privacy-
preserving P2P data sharing with OneSwarm. In Proc. of SIG-
COMM, 2010.
M. F. Kaashoek, D. R. Engler, G. R. Ganger, H. M. Briceno,
R. Hunt, D. Mazieres, T. Pinckney, R. Grimm, J. Jannotti, , and
K. Mackenzie. Application performance and flexibility in exoker-
nel systems. In Proc. of SOSP, 1997.
K. Keetong, D. Patterson, and J. Hellerstein. A case for intelligent
disks (IDISKs). ACM SIGMOD Record, 27(3), August 1998.
E. Kohler, R. Morris, B. Chen, J. Jannotti, and M. F. Kaashoek.
The click modular router. In Proc. of the 17th ACM Symp. on
Operating Systems Principles, December 1999.
C. Lesniewski-Lass and M. F. Kaashoek. Whanaungatanga:
Sybil-proof distributed hash table. In Proc. of NSDI, 2010.
N. A. Lynch, D. Malkhi, and D. Ratajczak. Atomic Data Access
in Distributed Hash Tables. In Proc. of IPTPS, 2001.
H. V. Madhyastha, T. Isdal, M. Piatek, C. Dixon, T. Anderson,
A. Krishnamurthy, and A. Venkataramani. iPlane: An Information
Plane for Distributed Services. In OSDI, 2006.
A. Muthitacharoen, S. Gilbert, and R. Morris. Etna: A fault-
tolerant algorithm for atomic mutable DHT data. Technical Re-
port MIT-LCS-TR-993, MIT, June 2005.
Mysql Database Triggers. http://dev.mysql.com/doc/
refman/5.0/en/triggers.html.
L. Peterson, A. Bavier, M. Fiuczynski, and S. Muir. Experiences
implementing PlanetLab. In Proc. of OSDI, 2006.
M. Piatek, T. Isdal, A. Krishnamurthy, and T. Anderson. Do in-
centives build robustness in BitTorrent? In NSDI, 2007.
Project Voldemort. http: //project-voldemort.com/.
V. Ramasubramanian and E. G. Sirer. Beehive: O(1) lookup per-
formance for power-law query distributions in peer-to-peer over-
lays. In Proc. of NSDI, Berkeley, CA, USA, 2004. USENIX As-
sociation.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
S. Rhea, B. Godfrey, B. Karp, J. Kubiatowicz, S. Ratnasamy,
S. Shenker, I. Stoica, and H. Yu. OpenDHT: A public DHT ser-
vice and its uses. In Proc. of SIGCOMM, 2005.
E. Riedel, G. Gibson, and C. Faloutsos. Active storage for large-
scale data mining and multimedia. In Proc. of 24th International
Conference on Very Large Databases, August 1998.
T. Ristenpart, E. Tromer, H. Shacham, and S. Savage. Hey, you,
get off of my cloud: exploring information leakage in third-party
compute clouds. In Proc. of CCS, 2009.
W. C. F. Roberto Ierusalimschy, Luiz Henrique de Figueiredo. Lua
- an extensible extension language. Software: Practice and Expe-
rience, 26(6):635—652, 1999.
A. Rowstron and P. Druschel. Storage management and caching
in PAST, a large-scale, persistent peer-to-peer storage utility. In
Proc. of SOSP, 2001.
A. I. T. Rowstron, A.-M. Kermarrec, M. Castro, and P. Druschel.
Scribe: The design of a large-scale event notification infrastruc-
ture. In Proc. of the Third International COST264 Workshop on
Networked Group Communication, 2001.
M. Seltzer, Y. Endo, C. Small, and K. Smith. Dealing With Dis-
aster: Surviving Misbehaved Kernel Extensions. In OSDI, 1996.
A. Singh, T.-W. Ngan, P. Druschel, , and D. Wallach. Eclipse
attacks on overlay networks: Threats and defenses. In IVFOCOM,
2006.
E. Sit and R. Morris. Security considerations for peer-to-peer dis-
tributed hash tables. In Proc. of IPTPS, 2002.
M. Steiner and E. W. Biersack. Crawling AZUREUS. Technical
report, Institut Eurecom, Networking and Security Department,
2008.
M. Steiner, E. W. Biersack, and T. Ennajjary. Actively monitoring
peers in KAD. In Proc. of IPTPS, 2007.
M. Steiner, T. En-Najjary, and E. W. Biersack. A Global View of
KAD. In Proc. of IMC, 2007.
I. Stoica, D. Adkins, S. Zhuang, S. S. nker, and S. Surana. Internet
indirection infrastructure. In Proc. of SIGCOMM, 2002.
D. Stutzbach and R. Rejaie. Understanding Churn in Peer-to-Peer
Networks. In Proc. of IMC, 2006.
D. L. Tennenhouse and D. J. Wetherall. Towards an active net-
work architecture. ACM SIGCOMM Computer Communications
Review, April 1996.
G. Urdaneta, G. Pierre, and M. V. Steen. A Survey of DHT Secu-
rity Techniques (to appear). ACM Computing Survey, 2010.
uTorrent. http: //www.utorrent.com.
T. von Eicken, D. E. Culler, S. C. Goldstein, and K. E. Schauser.
Active messages: a mechanism for integrated communication and
computation. In Proc. of ISCA, 1992.
Vuze, Inc. http://www. vuze.com.
P. Wang, I. Osipkov, N. Hopper, and Y. Kim. Myrmic: Secure and
Robust DHT Routing. Technical report, University of Minnesota,
2007.
D. Wetherall. Active network vision and reality: Lessons from a
capsule-based system. In Proc. of the 17th ACM Symp. on Oper-
ating Systems Principles, December 1999.
S. Wolchok, O. S. Hofmann, E. W. Felten, J. A. Halderman, C. J.
Rossbach, B. Waters, and E. Witchel. Defeating Vanish with low-
cost Sybil attacks against large DHTs. In Proc. of NDSS, 2010.
H. Xie, R. Yang, A. Krishnamurthy, Y. Liu, and A. Silberschatz.
P4P: Provider portal for P2P applications. In Proc. of SIGCOMM,
2008.
H. Yu, M. Kaminsky, P. B. Gibbons, and A. D. Flaxman. Sybil-
Guard: defending against sybil attacks via social networks. Proc.
of SIGCOMM, 2006.
S. Q. Zhuang, B. Y. Zhao, A. D. Joseph, R. H. Katz, and J. D. Ku-
biatowicz. Bayeux: an architecture for scalable and fault-tolerant
wide-area data dissemination. In Proc. of NOSSDAV, 2001.
USENIX Association
USENIX Association
SPORC: Group Collaboration using Untrusted Cloud Resources
Ariel J. Feldman, William P. Zeller, Michael J. Freedman, and Edward W. Felten
Princeton University
Abstract
Cloud-based services are an attractive deployment
model for user-facing applications like word processing
and calendaring. Unlike desktop applications, cloud ser-
vices allow multiple users to edit shared state concurrently
and in real-time, while being scalable, highly available,
and globally accessible. Unfortunately, these benefits
come at the cost of fully trusting cloud providers with
potentially sensitive and important data.
To overcome this strict tradeoff, we present SPORC, a
generic framework for building a wide variety of collabo-
rative applications with untrusted servers. In SPORC, a
server observes only encrypted data and cannot deviate
from correct execution without being detected. SPORC
allows concurrent, low-latency editing of shared state,
permits disconnected operation, and supports dynamic
access control even in the presence of concurrency. We
demonstrate SPORC’s flexibility through two prototype
applications: a causally-consistent key-value store and a
browser-based collaborative text editor.
Conceptually, SPORC illustrates the complementary
benefits of operational transformation (OT) and fork*
consistency. The former allows SPORC clients to execute
concurrent operations without locking and to resolve any
resulting conflicts automatically. The latter prevents a
misbehaving server from equivocating about the order of
operations unless it is willing to fork clients into disjoint
sets. Notably, unlike previous systems, SPORC can auto-
matically recover from such malicious forks by leveraging
OT’s conflict resolution mechanism.
1 Introduction
An emerging class of cloud-based collaborative services,
such as online document processing and calendaring, pro-
vides users with anywhere-available, real-time, and con-
current access to shared state. Their deployments on man-
aged cloud platforms enjoy global accessibility, high avail-
ability, fault tolerance, and elastic resource allocation and
scaling. Yet these benefits have come at the cost of having
a fully trusted server, creating a risk of privacy problems
due to server-side information leaks. The history of such
services is one rife with unplanned data disclosures and
malicious break-ins [24]. Indeed, the very centralization
of information makes cloud providers high value targets
for attack. Further, the behavior of service providers them-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
selves is a source of users’ privacy angst, as privacy poli-
cies may be weakened due to market expediencies. Finally,
cloud providers face pressure from government agencies
world-wide to release information on demand [15].
This paper challenges the belief that applications must
sacrifice strong security and privacy to enjoy the bene-
fits of cloud deployment. We present a system, SPORC,
that offers managed cloud-based deployment for group
collaboration services, yet does require users to trust the
cloud provider to maintain data privacy or even to oper-
ate correctly. SPORC’s cloud servers see only encrypted
data, and clients will detect any deviation from correct
operation (e.g., adding, modifying, dropping, or reorder-
ing operations) and will recover from the error. Much
like SUNDR [24], SPORC bases its security and privacy
guarantees on the security of users’ cryptographic keys,
and not on the cloud provider’s good intentions nor on
some threshold-like protocol between servers [9] that is
susceptible to administrative or software attacks.
SPORC provides a generic collaboration service in
which users can create a document, modify its access con-
trol list, edit it concurrently, experience fully automated
merging of updates, and even perform these operations
while disconnected. The SPORC framework supports a
broad range of collaborative applications. Data updates
are encrypted before being sent to a cloud-hosted server.
The server assigns a total order to all operations and re-
distributes the ordered updates to clients. If a malicious
server drops or reorders updates, the SPORC clients can
detect the server’s misbehavior, switch to a new server,
restore a consistent state, and continue. The same mech-
anism that allows SPORC to merge correct concurrent
operations also enables it to transparently recover from
attacks that fork clients’ views.
From a conceptual distributed systems perspective,
SPORC demonstrates the benefit of combining opera-
tional transformation [11] and fork* consistency proto-
cols [23]. Operational transformation (OT) defines a
framework for executing lock-free concurrent operations
that both preserves causal consistency and converges to a
common shared state. It does so by transforming opera-
tions so they can be applied commutatively by different
clients, resulting in the same final state. While OT origi-
nated with decentralized applications using pairwise rec-
onciliation [11, 18], recent systems like Google Wave [44]
have used OT with a trusted central server that orders and
transforms clients’ operations. Fork* consistency, on the
337
338
other hand, was introduced as a consistency model for
interacting with an untrusted server: If the server causes
the views of two clients to diverge, the clients must either
never see each others’ subsequent updates or else identify
the server as faulty.
Recovering from a malicious fork is similar to recon-
ciling concurrent operations in the OT framework. Upon
detecting a fork, SPORC clients use OT mechanisms to
replay and transform forked operations, restoring a consis-
tent state. Previous applications of fork* consistency [23]
could only detect forks, but not resolve them.
This paper makes the following contributions:
§2 We identify and explore the conceptual connection
between operational transformation protocols and the
fork* consistency model, and use this connection to
motivate SPORC’s design.
§3 We describe SPORC’s framework and protocols for
real-time collaboration. SPORC provides security
and privacy against both an untrusted server that me-
diates communication and other clients that lack ac-
cess control permissions.
84 We demonstrate how to support dynamic access con-
trol, which is challenging because SPORC supports
concurrent operations and offline editing.
85 We describe how clients can detect and recover
from maliciously-instigated forks. We also present a
checkpoint mechanism that reduces saved client state
and minimizes the join overhead for new clients.
86 We illustrate the extensibility of SPORC’s pluggable
data model by building both a key-value store and a
browser-based collaborative text editor. We imple-
ment these services as both stand-alone applications
and web services; the latter run in a browser, execute
in JavaScript (compiled from Java via GWT [12]),
and require no prior installation.
We evaluate SPORC’s performance in Section 7 before
discussing related work and concluding.
2 System Model
The purpose of SPORC is to allow a group of users who
trust each other to collaboratively edit some shared state,
which we call the document, with the help of an untrusted
server. SPORC is comprised of a set of client devices
that modify the document on behalf of particular users,
and a potentially-malicious server whose main role is to
impose a global order on those modifications. The server
receives updates from individual clients, orders them, and
then broadcasts them to the other clients. Access to the
document is limited to a set of authorized users, but each
user may be logged into arbitrarily many clients simul-
taneously (e.g., her desktop, laptop, and mobile phone).
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
Each client, even if it is controlled by the same user as
another client, has its own local view of the document that
must be synchronized with all other clients.
2.1 Goals
We designed SPORC with the following goals in mind:
Flexible framework for a broad class of collabora-
tive services. Because SPORC uses an untrusted server
which does not see application-level content, the server is
generic and can handle a broad class of applications. On
the client side, SPORC provides a library suitable for use
by a range of desktop and web-based applications.
Propagate modifications quickly. When a client is
connected to the network, its changes to the shared state
should propagate quickly to all other clients so that clients’
views are nearly identical. This property makes SPORC
suitable for building collaborative applications requiring
nearly real-time updates, such as collaborative text editing
and instant messaging.
Tolerate slow or disconnected networks. To allow
clients to edit the document while offline or while experi-
encing high network latency, clients in SPORC update the
document optimistically. Every time a client generates a
modification, the client applies it immediately to its local
state, and only later sends it to the server for redistribu-
tion. As a result, clients’ local views of the document will
invariably diverge, and SPORC must be able to resolve
these divergences automatically.
Keep data confidential from the server and unau-
thorized users. Since the server is untrusted, document
updates must be encrypted before being sent to the server.
For efficiency, the system should use symmetric-key en-
cryption. SPORC must provide a way to distribute this
symmetric key to every client of authorized users. When
a document’s access control list changes, SPORC must
ensure that newly added users can decrypt the entire docu-
ment, and that removed users cannot decrypt any updates
subsequent to their expulsion.
Detect a misbehaving server. Even without access to
document plaintext, a malicious server could still do signif-
icant damage by deviating from its assigned role. It could
attempt to add, drop, alter, or delay clients’ (encrypted)
updates, or it could show different clients inconsistent
views of the document. SPORC must give clients a means
to quickly detect these kinds of misbehavior.
Recover from malicious server behavior. If clients
detect that the server is misbehaving, clients should be
able to failover to a new server and resume execution.
Since a malicious server could cause clients to have incon-
sistent local state, SPORC must provide a mechanism for
automatically resolving these inconsistencies.
To achieve these goals, SPORC builds on two concep-
tual frameworks: operational transformation and fork*
consistency.
USENIX Association
USENIX Association
2.2 Operational Transformation
Operational Transformation (OT) [11] provides a general
model for synchronizing shared state, while allowing each
client to apply local updates optimistically. In OT, the
application defines a set of operations from which all
modifications to the document are constructed. When
clients generate new operations, they apply them locally
before sending them to others. To deal with the conflicts
that these optimistic updates inevitably incur, each client
transforms the operations it receives from others before
applying them to its local state. If all clients transform
incoming operations appropriately, OT guarantees that
they will eventually converge to a consistent state.
Central to OT is an application-specific transformation
function T(-) that allows two clients whose states have
diverged by a single pair of conflicting operations to re-
turn to a consistent, reasonable state. T(op, op2) takes
two conflicting operations as input and returns a pair of
transformed operations (op/,, op!,), such that if the party
that initially did op; now applies op’, and the party that
did op2 now applies op’, the conflict will be resolved.
To use the example from Nichols et al. [30], sup-
pose Alice and Bob both begin with the same local state
“ABCDE”, and then Alice applies op; = ‘del 4’ locally
to get “ABCE”, while Bob performs op2 = ‘del 2’ to
get “ACDE”. If Alice and Bob exchanged operations and
executed each others’ naively, then they would end up
in inconsistent states (Alice would get “ACE” and Bob
“ACD”). To avoid this problem, the application supplies the
following transformation function that adjusts the offsets
of concurrent delete operations:
(del x—1,del y) if a>y
T(del x,del y) = ¢ (del z,del y—1) if a<y
(no-op,no-op) if w=y
Thus, after computing T(op;,op2), Alice will apply
ops, =‘del 2’ as before but Bob will apply op, = ‘del 3’,
leaving both in the consistent state “ACE”.
Given this pair-wise transformation function, clients
that diverge in arbitrarily many operations can return to a
consistent state by applying the transformation function
repeatedly. For example, suppose that Alice has optimisti-
cally applied op; and opz to her local state, but has yet
to send them to other clients. If she receives a new op-
eration OPnew, Alice must transform it with respect to
both op; and ops: She first computes (op’,..,,,, 0p) —
T(Oprea op1); and then COD ets ops) — DOD casiss op2).
This process yields op’’.,,,, an operation that Alice has
“transformed past” her two local operations and can now
apply to her local state.
Throughout this paper, we use the notation op’ —
T (op, (opi,...,0Pn)) to denote transforming op past a
sequence of operations (op1,...,0p,,) by iteratively ap-
plying the transformation function.! Similarly, we de-
fine (op',,...,op),) <— T((opi,...,0Pn), op) to repre-
sent transforming a sequence of operations past a single
operation.
Operational transformation can be applied in a wide
variety of settings, as operations, and the transforms on
them, can be tailored to each application’s requirements.
For a collaborative text editor, operations may contain
inserts and deletes of character ranges at specific cursor
offsets, while for a causally-consistent key-value store,
operations may contain lists of keys to update or remove.
In fact, we have implemented both such systems on top of
SPORC, which we describe further in Section 6.
For many applications, with a carefully-chosen trans-
formation function, OT is able to automatically return
divergent clients to a state that is not only consistent, but
semantically reasonable as well. But for some applica-
tions, such as source-code version control, semantic con-
flicts must be resolved manually. OT can support such
applications through the choice of a transformation func-
tion that does not try to resolve the conflict, but instead
inserts an explicit conflict marker into the history of oper-
ations. A human can later examine the marker and resolve
the conflict by issuing new writes. These write operations
will supercede the conflicting operations, provided that the
system preserves the global order of committed operations
and the partial order of each client’s operations. Section 3
describes how SPORC provides these properties.
While OT was originally proposed for decentralized n-
way synchronization between clients, many prominent OT
implementations are server-centric, including Jupiter [30]
and Google Wave [44]. They rely on the server to resolve
conflicts and to maintain consistency, and are architec-
turally better suited for web services. On the flip side, a
misbehaving server can compromise the confidentiality,
integrity, and consistency of the shared state.
Later, we describe how SPORC adapts these server-
based OT architectures to provide security against a mis-
behaving server. At a high level, SPORC has each client
simulate the transformations that would have been applied
by a trusted OT server, using the server only for ordering.
But we still need to protect against inconsistent orderings,
for which we leverage fork* consistency techniques [23].
2.3. Fork* Consistency
To prevent a malicious server from forging or modifying
clients’ operations, clients in SPORC digitally sign all
their operations with their user’s private key. This is not
sufficient for correctness, however: a misbehaving server
could still equivocate and present different clients with
divergent views of the history of operations.
'Strictly speaking, T always returns a pair of operations. For sim-
plicity, however, we sometimes write T’ as returning a single operation,
especially when the other is unchanged, as in our “delete char” example.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) 339
340
To defend against server equivocation, SPORC clients
enforce fork* consistency [23].” In fork*-consistent sys-
tems, clients share information about their individual
views of the history by embedding it in every operation
they send. As a result, if clients to whom the server has
equivocated ever communicate, they will discover the
server’s misbehavior. The server can still divide its clients
into disjoint groups and only tell each client about oper-
ations by others in its group. But, once the server has
forked two groups in this way, it cannot tell a member
of one group about an operation submitted by another
group’s members without risking detection.
As in BFT2F [23], each SPORC client enforces fork*
consistency by maintaining a hash chain over its view of
the committed history. In this context, a hash chain is a
method of incrementally computing the hash of a list of
elements. More specifically, if op1,..., opp, are the opera-
tions in the history, Ao is a constant initial value, and h, is
the value of the hash chain over the history up to op,;, then
h; = H(hi-1||H(op;)), where H(-) is a cryptographic
hash function and || denotes concatenation. When a client
with history up to op, submits a new operation, it includes
hy in its message. On receiving the operation, another
client can check whether the included h,, matches its own
hash chain computation over its local history up to op,.
If they do not match, the client knows that the server has
equivocated.
2.4 The Benefits of Having a Server
SPORC uses a central untrusted server, but the server’s
sole purpose is to order and store client-generated opera-
tions. This limited role may lead one to ask whether the
server should be removed, leading to a completely peer-to-
peer design. Indeed, many group collaboration systems,
such as Bayou [43] and Network Text Editor [17], employ
decentralized architectures. Decentralized designs are a
poor fit, however, for applications in which a user needs a
timely notification that her operation has been committed
and will not be overridden by another’s (not yet received)
operation. For example, to schedule a meeting room, an
online user should be able to quickly determine whether
her reservation succeeded, without worrying if an offline
client’s request will override hers. Yet this is difficult to
achieve without waiting to hear from all (or at least a quo-
rum of) other clients, which poses a problem when clients
are regularly offline. In reaction, Bayou delegates com-
?Fork* consistency is a weaker variant of an earlier model called fork
consistency [27]. They differ in that under fork consistency, a pair of
clients only needs to exchange one message to detect server equivocation,
whereas under fork* consistency, they may need to exchange two. For
OT systems like ours, this distinction makes little difference because
clients constantly exchange small messages. On the other hand, fork*
consistency permits a one-round protocol to submit operations, rather
than two. Beyond efficiency, this also ensures that a crashed client cannot
prevent the system from making progress.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
mits to a (statically) designated, trusted “primary” peer,
which is little different from having a server.
SPORC, on the other hand, only requires an untrusted
server for globally ordering operations. Thus, it can lever-
age the benefits of a cloud deployment—high availability
and global accessibility—to achieve timely commits. We
show in Section 4.2 how SPORC’s centralized server also
helps support dynamic access control and key rotation,
even in the face of concurrent membership changes.
2.5 Deployment and Threat Model
Deployment Assumptions. While most of the paper dis-
cusses the SPORC protocol in terms of a single server and
a single document, we assume that a cloud-based SPORC
deployment would manage large numbers of users and
documents by replicating functionality and partitioning
state over many servers. Each document in SPORC can be
managed independently, leading naturally to the shared-
nothing architectures [36] already common to scalable
cloud services.
For a client to recover from a misbehaving server, we
assume there exists some alternative (untrusted) server
to switch to after a client detects faulty behavior. These
backup servers may belong to the same or different admin-
istrative domains as the original, depending upon the type
of faults that a SPORC deployment expects to encounter.
Note that even if malicious (Byzantine) behavior among
cloud servers is not a primary concern, this strong threat
model also covers weaker non-crash failures related to
server misconfiguration, Heisenbugs, or “split-brain” par-
titioned behavior. In all cases, failover and recovery is
client driven. Crash failures, unlike Byzantine failures,
would not result in forks and could be handled by tra-
ditional fault-tolerance techniques (e.g., primary/backup
replication) already employed in cloud services.
Threat Model.
assumptions:
Server: The server is potentially malicious, and a mis-
behaving server may be able to prevent progress, but it
must not be able to corrupt the clients’ shared state. A
server may fork clients’ states, but only within the con-
fines of the fork* consistency model. If clients are able to
communicate either in-band or out-of-band, server equiv-
ocation will be detected promptly by at least one client.
The server may be able to learn which users and clients
are sharing a document, but it must not learn what is in the
document or even the contents of the individual operations
that the clients submit. Since the server has access to the
size and timing of clients’ operations, it may be able to
glean some information about the document via traffic
analysis. Traffic analysis is made more difficult by the
fact that encrypted operations do not even reveal which
portions of the shared state they modify. Neverthless, the
complete mitigation of traffic analysis is beyond the scope
SPORC makes the following security
USENIX Association
USENIX Association
of this work, but it would likely involve padding the length
of operations and introducing cover traffic.
To attack availability, the server may arbitrarily erase
or refuse to return any of the encrypted data that it stores.
To mitigate this threat, the encrypted data could be repli-
cated on servers in other administrative domains. More-
over, each client could replicate its own local state on
cloud servers other than the main SPORC server. Notably,
SPORC cannot guarantee recovery from every possible
fork, unless every client stores every operation that it has
seen either locally or remotely.
Clients: If a client is logged in as a particular user, that
client is trusted to exercise the privileges granted to that
user (e.g., to see the state, modify it, or modify access
privileges). Otherwise, clients are untrusted, and they
should not be able to see the document, or to modify the
document or its access control list, even if they collude
with each other or with the server.
User authentication and keys: We assume that each
user has a secure public/private key pair, and that clients
have a secure way to verify the public key of other users.
Application code: We assume the presence of a code
authentication infrastructure that can verify that the appli-
cation code run by clients is genuine. This mechanism
might rely on code signing or on HTTPS connections to a
trusted server (different from the untrusted server used as
part of SPORC’s protocols).
3 System Design
This section describes SPORC’s design in more detail, in-
cluding its synchronization mechanisms and the measures
that clients implement to detect a malicious server that
may modify, reorder, duplicate, or drop operations. This
section assumes that the set of users and clients editing a
given document is fixed; we consider dynamic member-
ship in Section 4.
3.1 System Overview
The parties and stages involved with SPORC operations
are shown in Figure |. At a high level, the local state of
a SPORC application is synchronized between multiple
clients, using a server to collect updates from clients, order
them, then redistribute the client updates to others. There
are four types of state in the system.
(1) The local state is a compact representation of the
client’s current view of the document (e.g., the most recent
version of a collaborative-edited text).
(2) The encrypted history is the set of operations stored
at and ordered by the server. The payloads of operations
that change the contents of the document are encrypted
to preserve confidentiality. The server orders the opera-
tions oblivious to their payloads but aware of the previous
operations on which they causally depend.
O,
username
Server
,
payload,
signature
seqNo
Pe
Figure |: SPORC architecture and synchronization steps
Client
Application
Committed
History
From Srv
Library
(3) The committed history is the official set of (plain-
text) operations shared among all clients, as ordered by
the server. Clients derive this committed history from
the server’s encrypted history by transforming operations’
payloads to reflect any changes that the server’s ordering
might have caused.
(4) A client’s pending queue is an ordered list of the
client’s local operations that have already been applied
to its local state, but that have yet to be committed (i.e.,
assigned a sequence number by the server and added to
the client’s committed history).
SPORC synchronizes clients’ local state for a partic-
ular document using the following steps, also shown in
Figure 1. This section restricts its consideration to interac-
tions with a static membership and well behaved server;
we relax these restrictions in the next two sections, re-
spectively. The flow of local operations to the server is
illustrated by dashed blue arrows; the flow of operations
received from the server is shown by solid red arrows.
1. A client application generates an operation, applies
it to its local state immediately, and then places it at
the end of the client’s pending queue.
2. If the client does not currently have any operations
under submission, it takes its oldest queued operation
yet to be sent, op, assigns it a client sequence number
(clntSeqNo), embeds in it the global sequence number
of the last committed operation (prevSeqNo) along
with the corresponding hash chain value (prevHC),
encrypts its payload, digitally signs it, and transmits
it to the server. (As an optimization, if the client
has multiple operations in its pending queue, it can
submit them as a single batched operation.)
3. The server adds the client-submitted op to its en-
crypted history, assigning it the next available global
sequence number (seqNo). The server forwards op
with this seqNo to all the clients participating in the
document.
4. Upon receiving an encrypted operation op, the client
verifies its signature (V) and checks that its clntSe-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI’10) 341
342
qgNo, seqNo, and prevHC fields have the expected
values. If these checks succeed, the client decrypts
the payload (D) for further processing. If they fail,
the client concludes that the server is malicious.
5. Before adding op to its committed history, the client
must transform it past any other operations that had
been committed since op was generated (i.e., all those
with global sequence numbers greater than op’s prev-
SeqNo). Once op has been transformed, the client
appends op to the end of the committed history.
6. If the incoming operation op was one that the client
had initially sent, the client dequeues the oldest ele-
ment in the pending queue (which will be the uncom-
mitted version of op) and prepares to send its next
operation. Otherwise, the client transforms op past
all its pending operations and, conversely, transforms
those operations with respect to op.
7. The client returns the transformed version of the in-
coming operation op to the application. The applica-
tion then applies op to its local state.
SPORC maintains the following invariants with respect to
the system’s state:
Local Coherence: A client’s local state is equivalent
to the state it would be in if, starting with an initial empty
document, it applied, in order, all of the operations in its
committed history followed by all of the operations in its
pending queue.
Fork* Consistency: If the server is well behaved, all
clients’ committed histories are linearizable (i.e., for every
pair of clients, one client’s committed history is equal to
or a prefix of the other client’s committed history). If the
server is faulty, however, clients’ committed histories may
be forked [23].
Client-Order Preservation: The order that a non-
malicious server assigns to operations originating from
a given client must be consistent with the order that the
client assigned to those operations.
3.2 Operations
SPORC clients exchange two types of operations: docu-
ment operations, which represent changes to the content
of the document, and meta-operations, which represent
changes to document metadata such as the document’s
access control list. Meta-operations are sent to the server
in the clear, but the payloads of document operations are
encrypted under a symmetric key that is shared among
all of the clients but is unknown to the server. (See Sec-
tion 4.1 for a description of how this key is chosen and
distributed.) In addition, every operation is labeled with
the name of the user that created it and is digitally signed
by that user’s private key. All operations also contain a
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
unique client ID (clntID) that identifies from which of the
user’s client machines it came.
3.3. The Server’s Limited Role
Because the SPORC server is untrusted, its role is limited
to ordering and storing the operations that clients submit,
most of which are encrypted. The server stores the opera-
tions in its encrypted history so that new clients joining the
document or existing clients that have been disconnected
can request from the server the operations that they are
missing. This storage function is not essential, however,
and in principle it could be handled by a different party.
Notably, since the server does not have access to the
plaintext of document operations, the same generic server
implementation can be used for any application that uses
our protocol regardless of the kind of document being
synchronized.
3.4 Sequence Numbers and Hash Chains
SPORC clients use sequence numbers and a hash chain to
ensure that operations are properly serialized and that the
server is well behaved. Every operation has two sequence
numbers: a client sequence number (c/ntSeqNo) which is
assigned by the client that submitted the operation, and
a global sequence number (segNo) which is assigned by
the server. On receiving an operation, a client verifies
that the operation’s clntSeqNo is one greater than the last
clntSeqNo seen from the submitting client, and that the op-
eration’s seqNo is one greater than the last seqNo that the
receiving client saw. These sequence number checks en-
force the “client order preservation” invariant and ensure
that there are no gaps in the sequence of operations.
When a client uploads an operation opyew to the server,
the client sets Opnew’s prevSeqNo field to the global se-
quence number of the last committed operation, op,,, that
the client knows about. The client also sets Opnew’s pre-
VHC field to the value of the client’s hash chain over the
committed history up to op,,. A client who receives opnew
compares its prevHC with the client’s own hash chain com-
putation up to op,. If they match, the recipient knows that
its committed history is identical to the sender’s committed
history up to op, thereby guaranteeing fork* consistency.
A misbehaving server cannot modify the prevSeqNo or
prevHC fields, because they are covered by the submitting
client’s signature on the operation. The server can try
to tell two clients different global sequence numbers for
the same operation, but this will cause the two clients’
histories—and hence their future hash chain values—to
diverge, and it will eventually be detected.
To simplify the design, each SPORC client has at most
one operation “‘in flight’ at any time: only the operation
at the head of a client’s pending queue can be sent to the
server. Among other benefits, this rule ensures that oper-
ations’ prevSeqNo and prevHC values will always refer
USENIX Association
USENIX Association
to operations that are in the committed history, and not
to other operations that are “in flight.’ This restriction
could be relaxed, but only at considerable cost in complex-
ity. For similar reasons, other OT-based systems such as
Google Wave adopt the same rule [44].
Prohibiting more than one in-flight operation per client
is less restrictive than it might seem, as operations can be
combined or batched. Like Wave, SPORC includes an
application-specific composition function, which consoli-
dates two operations into one. This can be used iteratively
to combine a sequence of operations into a single one.
Further, it is straightforward to batch multiple operations
into a single logical operation, which is then submitted as
a unit. Because operations can be composed or batched, a
client can empty its pending queue every time it gets an
opportunity to submit an operation to the server.
3.5 Resolving Conflicts with OT
Once a client has validated an operation received from the
server, the client must use OT to resolve the conflicts that
may exist between the new operation and other operations
in the committed history and pending queue. These con-
flicts might have arisen for two reasons. First, the server
may have committed additional operations since the new
operation was generated. Second, the receiving client’s lo-
cal state might reflect uncommitted operations that reside
on the client’s pending queue but that other clients do not
yet know about.
Before a client appends an incoming operation opnew
to its committed history, it compares Opnew’s prevSeqNo
value with the global sequence number of the last com-
mitted operation. The prevSeqNo field indicates the last
committed operation that the submitting client knew about
when it uploaded opyew. Thus, if the values match, the
client knows that no additional operations have been added
to its committed history since opnew was generated, and
the new operation can be appended directly to the commit-
ted history. But if they do not match, then other operations
were committed since Opnew was sent, and Oopnew needs
to be transformed past each of them. For example, if
OPnew has a prevSeqNo of 10, but was assigned global
sequence number 14 by the server, then the client must
compute op’...,, — T(0Pnew; (0P11, OP12; OP13)) where
(0p11, OP12, Op13) are the intervening committed opera-
tions. Only then can the resulting transformed operation
OPp},ew be appended to the committed history. After ap-
pending the operation, the client updates the hash chain
computed over the committed history so that future incom-
ing operations can be validated.
At this point, if op’,..,, is one of the receiving client’s
own operations that it had previously uploaded to the
server (or a transformed version of it), it will necessarily
match the operation at the head of the pending queue.
Since op’,.., has now been committed, its uncommitted
version can be retired from the pending queue, and the
next pending operation can be submitted to the server.
Furthermore, since the client has already optimistically
applied the operation to its local state even before sending
it to the server, the client does not need to apply op/,..,
again, and nothing more needs to be done.
If op’. 18 not one of the client’s own operations, how-
ever, the client must perform additional transformations in
order to reestablish the “local coherence” invariant, which
states that the client’s local state is equal to the in-order
application of its committed history followed by its pend-
ing queue. First, in order to obtain a version of op}, .,,, that
it can apply to its local state, the client must transform
OP},ew past all of the operations in its pending queue. This
step is necessary because the pending queue contains oper-
ations that the client has already applied locally, but have
not yet been committed and, therefore, were unknown to
the sender of op/,..,,,-
Second, the client must transform the entire pend-
ing queue with respect to op/,.,, to account for the
fact that op,..,, Was appended to the committed history.
More specifically, the client computes (op},..., op/,,) —
T((Opi,..-,0Pm), Diem) Where (Opi,...,OPm) is the
pending queue. This transformation has the effect of push-
ing the pending queue forward by one operation to make
room for the newly extended committed history. The op-
erations on the pending queue need to stay ahead of the
committed history because they will receive higher global
sequence numbers than any of the currently committed
operations. Furthermore, by transforming its unsent oper-
ations in response to updates to the document, the client
reduces the amount of transformation that other clients
will need to do when they eventually receive its operations.
4 Membership Management
Document membership in SPORC is controlled at the
level of users, each of which is associated with a public-
private key pair. When a document is first created, only the
user that created it has access. Subsequently, privileged
users can change the document’s access control list (ACL)
by submitting Modi fyUserOp meta-operations, which
get added to the document’s history (covered by its hash
chain), much like normal operations.
A user can be given one of three privilege levels:
reader, which entitles the user to decrypt the document but
not to submit new operations; editor, which entitles the
user to read the document and to submit new operations
(except those that change the ACL); and administrator,
which grants the user full access, including the ability
to invite new users and remove existing users. Because
ModifyUserOps are not encrypted, a non-malicious
server will immediately reject operations from users with
insufficient privileges. But because the server is untrusted,
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) = 343
344
every client maintains its own copy of the ACL, based
on the history’s Modi fyUserOps, and refuses to apply
operations that came from unauthorized users.
4.1 Encrypting Document Operations
To prevent eavesdropping by the server or unapproved
users, the payloads of document operations are encrypted
under a symmetric key known only to the document’s
current members. More specifically, to create a new docu-
ment, the creator generates a random AES key, encrypts it
under her own public key, and then writes the encrypted
key to the document’s initial create meta-operation. To add
new users, an administrator submits a ModifyUserOp
that includes the document’s AES key encrypted under
each of the new users’ public keys.
If users are removed, the AES key must be changed
so that the removed users will not be able to decrypt sub-
sequent operations. To do so, an administrator picks a
new random AES key, encrypts it under the public keys
of all the remaining participants, and then submits the
encrypted keys as part of the ModifyUserOp.? This
meta-operation also includes an encryption of the old AES
key under the new AES key. This enables later users to
learn earlier keys and thus decrypt old operations, without
requiring the operations to be re-encrypted.
SPORC’s model ensures proper access control over
operations, based on how it tracks potential causality
through prevSeqNo dependencies. Operations concurrent
to a ModifyUserOp removal may be ordered before
it and remain accessible to the user. However, once a
client sees the removal meta-operation in its committed
history any subsequent operation the client submits will
be inaccessible to the removed user.
4.2 Barrier Operations
Concurrency also poses a challenge to membership man-
agement. Consider the situation when two clients con-
currently issue ModifyUserOps that both attempt to
change the current symmetric key. If the server naively
scheduled one after the other, then the continuous chain
of old keys encrypted under new ones would be broken.
To address situations like this, we introduce a primitive
called a barrier operation. When the server receives an
operation that is marked “barrier” and assigns it global
sequence number }, the server requires that every sub-
sequent operation have a prevSeqNo > b. Subsequent
operations that do not are rejected and must be revised and
resubmitted with a later prevSeqNo. In this way, the server
3Tn our current implementation, the size of a Modi fyUserOp may
be linear in the number of users participating in the document, because
the operation may contain the current AES key encrypted under each
of the users’ RSA public keys. An optimization to achieve constant-
sized ModifyUserOps could instead use a space-efficient broadcast
encryption scheme [6].
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
can force all future operations to depend on the barrier
operation.‘
Let us reconsider the example of two concurrent
ModifyUseroOps, op, and opg, that are marked as barri-
ers. Suppose that the server received op, first and assigned
it sequence number b. Since the operations were submit-
ted concurrently, op2’s prevSeqNo will necessarily be less
than b, and op, will be rejected. The client attempting to
send op2 must wait until it receives op;, at which time it
will adjust op2 to depend on this operation before resub-
mitting (i.e., encrypt op;’s key under its new key, and set
op2’s prevSeqNo => b). As a result, the chain of old keys
encrypted under new ones will be preserved.
Barrier operations have uses beyond membership man-
agement. For example, as described next, they are useful
in implementing checkpoints on the history.
5 Extensions
This section describes extensions to the basic SPORC
protocols: supporting checkpoints to reduce the size re-
quirements for storing the committed history (Section 5.1),
detecting forks through out-of-band communication (Sec-
tion 5.2), and recovering from forks by replaying and pos-
sibly transforming forked operations (Section 5.3). Our
current prototype does not yet implement these extensions,
however.
5.1 Checkpoints
In order to reach a document’s latest state, a new client in
our current implementation must download and apply the
entire history of committed operations. It would be more
efficient for a new client to instead download a check-
point of operations—a compact representation of the doc-
ument’s state, akin to each client’s local state—and then
only apply individual committed operations since the last
checkpoint. Much as SPORC servers cannot transform
operations, they similarly cannot perform checkpoints;
SPORC once again has individual clients play this role.
To support checkpoints, each client maintains a com-
pacted version of the committed history up to the most
recent barrier operation. When a client is ready to upload
a checkpoint to the server, it encrypts this compacted his-
tory under the current document key. It then creates a new
CheckpointOp meta-operation containing the hash of
the encrypted checkpoint data and submits it into the his-
tory. Requiring the checkpoint data to end in a barrier
operation ensures that clients that later use the checkpoint
will be able to ignore the history before the barrier without
having to worry that they will need to perform OT transfor-
mations involving that old history. After all, no operation
4To prevent a malicious server from violating the rules governing
barrier operations, an operation’s “‘barrier” flag is covered by the opera-
tion’s signature, and all clients verify that the server is handling barrier
operations correctly.
USENIX Association
USENIX Association
after a barrier can depend on an operation before it. If the
most recent barrier is too old, the client can submit a new
null barrier operation before creating the checkpoint.>
Checkpoints raise new security challenges, however. A
client that lacks the full history cannot verify the hash
chain all the way back to the document’s creation. It can
verify that the operations it has chain together correctly,
but the first operation in its history (i.e., the barrier op-
eration) is “dangling,” and its prevHC value cannot be
verified. This is not a problem if the client knows in
advance that the Checkpoint Op is part of the valid his-
tory, but this is difficult to verify. The CheckpointOp
will be signed by a user, and users who have access to the
document are assumed to be trusted; but there must be a
way to verify that the signing user had permission to ac-
cess the document at the time the checkpoint was created.
Unfortunately, without access to a verifiable history of
individual ModifyUserOps going back the beginning
of the document, a client deciding whether to accept a
checkpoint has no way to be certain of which users were
actually members of the document at any given time.
To address these issues, we propose that the server and
clients maintain a meta-history, alongside the committed
history, that is comprised solely of meta-operations. Meta-
operations are included in the committed history as before,
but each one also has a prevMetaSeqNo pointer to a prior
element of the meta-history along with a corresponding
prevMetaHC field. Each client maintains a separate hash
chain over the meta-history and performs the same consis-
tency checks on the meta-history that it performs on the
committed history.
When a client joins, before it downloads a check-
point, it requests the entire meta-history from the server.
The meta-history provides the client with a fork* con-
sistent view of the sequence of ModifyUserOps and
CheckpointOps that indicates whether the check-
point’s creator was an authorized user when the checkpoint
was created. Moreover, the cost of downloading the entire
meta-history is likely to be low because meta-operations
are rare relative to document operations.
5.2. Checking for Forks Out-of-Band
Fork* consistency does not prevent a server from forking
clients’ state, as long as the server never tells any member
of one fork about any operation done by a member of an-
other fork. To detect such forks, clients can exchange state
information out-of-band, for example, by direct socket
SHaving the checkpoint data end in an earlier barrier operation
is better than making CheckpointOps into barriers themselves. If
Checkpoint Ops were barriers, then either the client making the check-
point would have to “lock” the history to prevent new operations from
being admitted before the checkpoint was uploaded, or the system would
have to reject checkpoints that did not reflect the latest state, which could
potentially lead to livelock.
connections, email, instant messaging, or posting on a
shared server or DHT service.
Clients can exchange messages of the form (c, d, 5, hs),
asserting that in client c’s view of document d, the hash
chain value as of sequence number s is equal to hs. On
receiving such a message, a client compares its own hash
chain value at sequence number s with h,, and if the
values differ, it knows a fork has occurred. If the recipient
does not yet have operations up to sequence number s, it
requests them from the server; a well behaved server will
always be able to supply the missing operations.
These out-of-band messages should be digitally signed
to prevent forgery. To prevent out-of-band messages from
leaking information about which clients are collaborat-
ing on a document, and to prevent a client from falsely
claiming that it was invited into the document by a forked
client, the out-of-band messages should be encrypted and
MACed with a separate set of symmetric keys that are
known only to nodes that have been part of the document.°
These keys might be conveyed in the first operation of the
document’s history.
5.3. Recovering from a Fork
A benefit of combining OT and fork* consistency is that
we can use OT to recover from forks. OT is well suited
to this task because, in normal operation, OT clients are
essentially creating small forks whenever they optimisti-
cally apply operations locally, and resolving these forks
when they transform operations to restore consistency. In
this section, we sketch an algorithm that a pair of forked
clients can use to merge their divergent histories into a
consistent whole. This pairwise algorithm can be repeated
as necessary to resolve forks involving multiple clients, or
multi-way forks.
The basic idea of the algorithm is that the two clients
will abandon the malicious server and agree on a new
one. Both clients will roll back their histories to their last
common point before the fork, and one of them will upload
the common history, up to the fork point, to the new server.
Finally, each client will resubmit the operations that it
saw after the fork. OT will ensure that these resubmitted
operations are merged safely so that both nodes end up in
the same state.
The situation becomes more complicated if the same
operation appears in both histories. We cannot just remove
the duplicate because later operations in the sequence may
depend on it. Instead, we must cancel it out. To make
this possible, we require that all operations be invertible:
6A client falsely claiming to have been invited into the document
in another fork will eventually be detected when the other clients try
to recover from the (false) fork. However, this is expensive so we
would prefer to avoid it. By protecting the out-of-band messages with
symmetric keys known only to clients who have been in the document at
some point, we reduce the set of potential liars substantially.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) = 345
346
we must be able to construct an inverse operation op™!
such that applying op followed by op~* results in a no-
op. This is often easy to do in practice by having each
operation store enough information about the prior state
to determine what the inverse should be. For example, a
delete operation can store the information that was deleted,
enabling the creation of an insert operation as the inverse.
To cancel each duplicate, we cannot simply splice its
inverse into the history right after it for the same reason
that we cannot just remove the duplicate. Instead, we
compute the inverse operation and then transform it past
all of the operations following the duplicate. This process
results in an operation that has the effect of canceling out
the duplicate when appended to the end of the sequence.
6 Implementation
SPORC provides a framework for building collaborative
applications that need to synchronize different kinds of
state between clients. It consists of a generic server im-
plementation and client-side libraries that implement the
SPORC protocol, including the sending, receiving, en-
cryption, and transformation of operations, as well as the
necessarily consistency checks and document membership
management. To build applications within the SPORC
framework, a developer only needs to implement client-
side functionality that (i) defines a data type for SPORC
operations, (ii) defines how to transform a pair of opera-
tions, and (iii) defines how to combine multiple document
operations into a single one. The server need not be modi-
fied, as it always deals with operations on encrypted data.
6.1 Variants
We implemented two variants of SPORC: a command-line
version in which both client and server are stand-alone
applications, and a web-based version with a browser-
based client and a Java servlet. The command-line ver-
sion, which we use for later microbenchmarks, is written
in approximately 5500 lines of Java code (per SLOC-
Count [46]) and, for network communication, uses the
socket-based RPC library in the open-source release of
Google Wave [16]. Because the server’s role is limited to
ordering and storing client-supplied operations, its basic
implementation is simple and only requires approximately
300 lines of code.
The web-based version shares the majority of its code
with the command-line variant. The server just encap-
sulates the command-line server functionality in a Java
servlet. The client consists almost entirely of JavaScript
code that was automatically generated using the Java-to-
JavaScript compiler included with the Google Web Toolkit
(GWT) [12]. Network communication uses a combina-
tion of the GWT RPC framework, which wraps browser
XmlHttpRequests, and the GWTEventService [37],
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
which allows the server to push messages to the browser
asynchronously through a long-lived HTTP connection
(the so-called “Comet” style of web programming). This
prototype could be extended with HTMLS’s offline stor-
age to provide disconnected operation.
The client’s use cryptographic module was its only
component that could not be translated to JavaScript.
JavaScript remains too slow to implement public key cryp-
tography efficiently, and browsers lack both secure storage
for cryptographic keys and a secure pseudorandom num-
ber generator for key generation. To work around these
limitations, we encapsulate our cryptographic module in a
Java applet and implement JavaScript-to-Java communica-
tion using the LiveConnect API [28] (a strategy employed
in [2, 47]). Our experience suggests it would be beneficial
for browsers to provide a JavaScript API that supported
basic cryptographic primitives.
6.2 Building SPORC Applications
To demonstrate the usefulness of our framework, we
built two prototype applications: a causally-consistent
key-value store and a web-based collaborative text editor.
The key-value store keeps a simple dictionary—mapping
strings to strings—synchronized across a set of partici-
pating clients. To implement it, we defined a data type
that represents a list of keys to update or remove. We
wrote a simple transformation function that implements a
“last writer wins” policy, as well as a composition function
that merges two lists of key updates in a straightforward
manner. Overall, the application-specific portion of the
key-value store only required 280 lines of code.
The collaborative editor allows multiple users to modify
a text document simultaneously via their web browsers
and see each other’s changes in near real-time. It pro-
vides a user experience similar to Google Docs [14] and
EtherPad [13], but, unlike those services, it does not re-
quire a trusted server. To implement it, we were able to
reuse the data types and the transformation and compo-
sition functions from the open-source release of Google
Wave. Although Wave is a server-centric OT system with-
out SPORC’s level of security and privacy, we were able
to adapt its components for our framework with only 550
lines of wrapper code.
7 Experimental Evaluation
The user-facing collaborative applications for which
SPORC was designed—e.g., word processing, calendar-
ing, and instant messaging—require latency that is low
enough for human users to see each others’ updates in
real-time. But unlike file or storage systems, their primary
goal is not high throughput. In this section, we present
the results of several microbenchmarks of our Java-based
USENIX Association
USENIX Association
Server crypto
Server processing
Client processing
Client transformation
Client queueing
Client crypto
Mean latency (ms)
1 2 4 8 16
Number of clients
(a) Unloaded key-value store
Server crypto
Server processing
Mean latency (ms)
Client processing
Client transformation
Client queueing
Client crypto
8
Number of clients
(b) Unloaded text editor
Figure 2: Latency of SPORC with a single client writer
command-line version, to demonstrate SPORC’s useful-
ness for this class of applications.
We performed our experiments on a cluster of five com-
modity machines, each with eight 2.3 GHz AMD Opteron
cores and 8 GB of RAM, that were connected by gigabit
switched Ethernet. In each of our experiments, we ran a
single server instance on its own machine, along with vary-
ing numbers of client instances. To scale our system to
moderate numbers of clients, in many of our experiments,
we ran multiple client instances on each machine. We ran
all the experiments under the OpenJDK Java VM (version
IcedTea6 1.6). For RSA signatures, however, we used
the Network Security Services for Java (JSS) library from
the Mozilla Project [29] because, unlike Java’s default
cryptography library, it is implemented in native code and
offers considerably better performance.
Latency. To measure SPORC’s latency, we conducted
three minute runs with between one and sixteen clients for
both key-value and text editor operations. We tested our
system under both low-load conditions, where only one of
the clients submitted new operations (once every 200 ms),
and high-load conditions, where all of the clients were
writers. We measured latency by computing the mean
time that an operation was “in flight”: from the time that it
was generated by the sender’s application-level code, until
the time it was delivered to the recipient’s application.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
Server crypto
Mean latency (ms)
Server processing
Client processing
Client transformation
Client queueing
Client crypto
1 2 4 8 16
Number of clients
(a) Loaded key-value store
N
N
oO
BR
u
Server crypto
Server processing
Client processing
Client transformation
Client queueing
Client crypto
Mean latency (ms)
BH
8
Number of clients
(b) Loaded text editor
Figure 3: Latency of SPORC with all clients issuing writes
Under low-load conditions with only one writer, we
would expect the load on each client to remain constant as
the number of clients increases, because each additional
client does not add to the total number of operations in
flight. We would, however, expect to see server latency
increase modestly, as the server has to send operations
to increasing numbers of clients. Indeed, as shown in
Figure 2, the latency due to server processing increased
from under | ms with one client to over 3 ms with sixteen
clients, while overall latency increased modestly from
approximately 19 ms to approximately 25 ms.’
On the other hand, when every client is a writer, we
would expect the load on each client to increase with the
number of clients. As expected, Figure 3 shows that with
sixteen clients under loaded conditions, overall latency
is higher: approximately 26 ms for key-value operations
and 33 ms for the more expensive text-editor operations.
The biggest contributor to this increase is client queue-
ing, which is primarily the time that a client’s received
operations spend in its incoming queue before being pro-
cessed. Queueing delay begins at around 3 ms for one
7Figure 2 also shows small increases in the latency of client pro-
cessing and queuing when the number of clients was greater than four.
These increases are most likely due to the fact that, when we conducted
experiments with more than four clients, we ran multiple client instances
per machine.
347
348
1700 25
z 1600 -
8 1500 es
% 1400 Ss
3 1300 ISS
n Q.
g 1200 10 &
‘S 1100 2
3 z
2 1000 5 £
© 900
800 0
0 2 4 6 8 10 12 14 #16
Payload size (KB)
Figure 4: Server throughput as a function of payload size.
client and then increases steadily until it levels off at ap-
proximately 8 ms for the key-value application and 14 ms
for the text editor. Despite this increase, Figure 3 demon-
strates that SPORC successfully supports real-time col-
laboration for moderately-sized groups, even under load.
As these experiments were performed on a local-area net-
work, a wide-area deployment of SPORC would see an
increase in latency that reflects the correspondingly higher
network round-trip-time.
Figures 2 and 3 also show that client-side cryptographic
operations account for a large share of overall latency.
This occurs because SPORC performs a 2048-bit RSA sig-
nature on every outgoing operation and because Mozilla
JSS, while better than Java’s cryptography built-in library,
still requires about 10 ms to compute a single signature.
Using an optimized implementation of a more efficient sig-
nature scheme, such as ESIGN, could improve the latency
of signatures by nearly two orders of magnitude [24].
Server throughput. We measured the server’s maximum
throughput by saturating the server with operations using
100 clients. These particular clients were modified to
allow them to have more than one operation in flight at
a time. Figure 4 shows server throughput as a function
of payload size, measured in terms of both operations
per second and MB per second. Each data point was
computed by performing a three minute run of the system
and then taking the median of the mean throughput of
each one second interval. The error bars represent the 5th
and 95th percentiles. The figure shows that, as expected,
when payload size increases, the number of operations per
second decreases, because each operation requires more
time to process. But, at the same time, data throughput
(MB/s) increases, because the processing overhead per
byte decreases.
Client time-to-join. Because our current implementa-
tion lacks the checkpoints of Section 5.1, when a client
joins the document, it must first download each individ-
ual operation in the committed history. To evaluate the
cost of joining an existing document, we first filled the
history with varying numbers of operations. Then, we
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
20 1 1 1
votee-t Text Editor (w/ pending) 4
women Key-Value (w/ pending)
———+ Text Editor
eee a--- Key-Value
Client time-to-join time (s)
=
oO
0 2000 4000 6000 8000 10000
Number of committed operations
Figure 5: Client time-to-join given a variable length history
measured the time it took for a new client to receive the
shared decryption key and download and process all of
the committed operations. We performed two kinds of
experiments: one where the client started with an empty
local state, and a second in which the client had 2000
pending operations that had yet to be submitted to the
server. The purpose of the second test was to measure
how long it would take for a client that had been work-
ing offline for some length of time to synchronize with
the current state of the document. Synchronization re-
quires the client to transform its pending operations past
the committed operations that the client has not seen; thus,
it is more costly than joining a document with an empty
local state. Notably, since the-fork recovery algorithm
sketched in Section 5.3 relies on the same mechanism that
is used to synchronize clients that have been offline—it
treats operations after the fork as if they were pending
uncommitted operations—this test also sheds light on the
cost of recovering from a fork.
Figure 5 shows time-to-join as a function of history size.
Each data point represents the median of ten runs, and the
error bars correspond to the 10th and 90th percentiles. We
find that time-to-join is linear in the number of committed
operations. It takes a client with an empty local state
approximately one additional second to join a document
for every additional 1000 committed operations.
In addition, the figure shows that the time-to-join with
a significant number of pending operations varies greatly
by application. In the key-value application, the transfor-
mation function is cheap, because it is effectively a no-op
if the given operations do not affect the same keys. As
a result, the cost of transforming 2000 operations adds
little to the time-to-join. By contrast, the text editor’s
more complex transformation function adds a non-trivial,
although still acceptable, amount of overhead.
8 Related Work
Real-time “groupware” collaboration systems have
adapted classic distributed systems techniques for time-
stamping and ordering (e.g., [4, 5, 20]), but have also
introduced novel techniques to automatically resolve
USENIX Association
USENIX Association
conflicts between concurrent operations in an intention-
preserving manner (e.g., [11, 18, 33, 38, 39, 40, 41, 42]).
These techniques form the basis of SPORC’s client syn-
chronization mechanism and allow it to support slow or
disconnected networks. Several systems also use OT to im-
plement undo functionality (e.g., [32, 33]), and SPORC’s
fork recovery algorithm draws upon these approaches.
Furthermore, as an alternative to OT, Bayou [43] allows
applications to specify conflict detection and merge pro-
tocols to reconcile concurrent operations. Most of these
protocols focus on decentralized settings and use n-way
reconciliation, but several well-known systems use a cen-
tral server to simplify synchronization between clients
(including Jupiter [30] and Google Wave [44]). SPORC
also uses a central server for ordering and storage, but al-
lows the server to be untrusted. Secure Spread [3] presents
several efficient message encryption and key distribution
architectures for such client-server group collaboration
settings. But unlike SPORC, it relies on trusted servers
that can generate keys and re-encrypt messages as needed.
Traditionally, distributed systems have defended against
potentially malicious servers by replicating functional-
ity and storage over multiple servers. Protocols, such
as Byzantine fault tolerant (BFT) replicated state ma-
chines [9, 21, 48] or quorum systems [1, 26], can then
guarantee safety and liveness, provided that some fraction
of these servers remain non-faulty. Modern approaches
optimize performance by, for example, concurrently exe-
cuting independent operations [19], permitting client-side
speculation [45], or supporting eventual consistency [35].
BFT protocols face criticism, however, because when the
number of correct servers falls below a certain threshold
(typically two-thirds), they cannot make progress.
Subsequently, variants of fork consistency protocols
(e.g., [7, 27, 31]) have addressed the question of how
much safety one can achieve with a single untrusted server.
These works demonstrate that server equivocation can al-
ways be detected unless the server permanently forks the
clients into groups that cannot communicate with each
other. SUNDR [24] and FAUST [8] use these fork consis-
tency techniques to implement storage protocols on top
of untrusted servers. Other systems, such as A2M [10]
and TrInc [22], rely on trusted hardware to detect server
equivocation. BFT2F [23] combines techniques from
BFT replication and SUNDR to achieve fork* consistency
with higher fractions of faulty nodes than BFT can resist.
SPORC borrows from the design of BFT2F in its use of
hash chains to limit equivocation, but unlike BFT2F or
any of these other systems, SPORC allows disconnected
operation and enables clients to recover from server equiv-
ocation, not just detect it.
Like SPORC, two very recent systems, Venus [34] and
Depot [25], allow clients to use a cloud resource without
having to trust it, and they also support some degree of
disconnected operation. Venus provides strong consis-
tency in the face of a potentially malicious server, but
does not support applications other than key-value storage.
Furthermore, unlike SPORC, it requires the majority of
a “core set” of clients to be online in order to achieve
most of its consistency guarantees. In addition, although
members may be added dynamically to the group editing
the shared state, it does not allow access to be revoked,
nor does it provide a mechanism for distributing encryp-
tion keys. Depot, on the other hand, does not rely on
the availability of a “core set” of clients and supports var-
ied applications. Moreover, similar to SPORC, it allows
clients to recover from malicious forks using the same
mechanism that it uses to keep clients synchronized. But
rather than providing a means for reconciling conflicting
operations as SPORC does with OT, Depot relies on the
application for conflict resolution. Because Depot treats
clients and servers identically, it can also tolerate faulty
clients, in addition to faulty servers. Unlike SPORC, how-
ever, Depot does not consider dynamic access control or
confidentiality.
9 Conclusion
Our original goal for SPORC was to design a general
framework for web-based group collaboration that could
leverage cloud resources, but not be beholden to them
for privacy guarantees. This goal leads to a design in
which servers only store encrypted data, and each client
maintains its own local copy of the shared state. But when
each client has its own copy of the state, the system must
keep them synchronized, and operational transformation
provides a way do to so. OT enables optimistic updates
and automatically reconciles clients’ conflicting states.
Supporting applications that need timely commits re-
quires a central server. But if we do not trust the server
to preserve data privacy, we should not trust it to commit
operations correctly either. This requirement led us to
employ fork* consistency techniques to allow clients to
detect server equivocation about the order of committed
operations. But beyond the benefits that each provides
independently, this work shows that OT and fork* consis-
tency complement each other well. Whereas prior systems
that enforced fork* consistency alone were only able to
detect malicious forks, by combining fork* consistency
with OT, SPORC can recover from them using the same
mechanism that keeps clients synchronized.
In addition to these conceptual contributions, we present
a membership management architecture that provides dy-
namic access control and key distribution with an un-
trusted server, even in the face of concurrency. Finally,
we also demonstrate the flexibility of our design by imple-
menting two applications: a causally-consistent key-value
store and a browser-based collaborative text editor.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) 349
350
Acknowledgments. We thank Siddhartha Sen, Jinyuan
Li, Alma Whitten, Alexander Shraer, and Christian Cachin
for their insights. We also thank our shepherd, Lidong
Zhou, and the anonymous reviewers for their helpful com-
ments. This research was supported by funding from
Google and the NSF CAREER grant CNS-0953197.
References
[1]
[2
far}
(11)
[12]
[13]
[14]
[15]
[16]
[17]
[18]
[19]
[20]
[21]
[22]
[23]
[24]
M. Abd-El-Malek, G. Ganger, G. Goodson, M. Reiter, and J. Wylie.
Fault-scalable byzantine fault-tolerant services. In Proc. SOSP,
Oct. 2005.
B. Adida. Helios: Web-based open-audit voting. In Proc. USENIX
Security, Aug. 2008.
Y. Amir, C. Nita-rotaru, J. Stanton, and G. Tsudik. Secure spread:
An integrated architecture for secure group communication. JEEE
Trans. Dependable and Secure Computing, 2:248—261, 2005.
P. Bernstein, N. Goodman, and V. Hadzilacos. Concurrency Con-
trol and Recovery in Database Systems. Addison-Wesley, 1987.
K. Birman, A. Schiper, and P. Stephenson. Lightweight causal
and atomic group multicast. ACM Trans. Comp. Systems, 9(3):
272-314, Aug. 1991.
D. Boneh, C. Gentry, and B. Waters. Collusion resistant broadcast
encryption with short ciphertexts and private keys. In Advances in
Cryptology — CRYPTO, Aug. 2005.
C. Cachin, A. Shelat, and A. Shraer. Efficient fork-linearizable
access to untrusted shared memory. In Proc. PODC, Aug. 2007.
C. Cachin, I. Keidar, and A. Shraer. Fail-aware untrusted storage.
In Proc. Dependable Systems and Networks (DSN), June 2009.
M. Castro and B. Liskov. Practical Byzantine fault tolerance. In
Proc. OSDI, Feb. 1999.
B.-G. Chun, P. Maniatis, S. Shenker, and J. Kubiatowicz. Attested
append-only memory: Making adversaries stick to their word. In
Proc. SOSP, Oct. 2007.
C. Ellis and S. Gibbs. Concurrency control in groupware systems.
ACM SIGMOD Record, 18(2):399-407, 1989.
Google. Google Web Toolkit (GWT). http: //code.google.
com/webtoolkit/, 2010.
Google. EtherPad. http: //etherpad.com/, 2010.
Google. Google Docs. http: //docs.google.com/, 2010.
Google. Government requests directed to Google and YouTube.
http://www.google.com/governmentrequests/,
2010.
Google. Google Wave federation protocol.
google.com/p/wave-protocol/, 2010.
M. Handley and J. Crowcroft. Network text editor (NTE): A
scalable shared text editor for MBone. In Proc. SIGCOMM, Oct.
1997.
A. Karsenty and M. Beaudouin-Lafon. An algorithm for distributed
groupware applications. In Proc. ICDCS, May 1993.
R. Kotla and M. Dahlin. High-throughput Byzantine fault tolerance.
In Proc. Dependable Systems and Networks (DSN), June 2004.
L. Lamport. Time, clocks, and the ordering of events in a dis-
tributed system. Comm. ACM, 21(7):558-565, 1978.
L. Lamport, R. Shostak, and M. Pease. The Byzantine generals
problem. ACM Trans. Programming Language Systems, 4(3),
1982.
D. Levin, J. R. Douceur, J. R. Lorch, and T. Moscibroda. TrInc:
Small trusted hardware for large distributed systems. In Proc.
NSDI, Apr. 2009.
J. Li and D. Maziéres. Beyond one-third faulty replicas in Byzan-
tine fault tolerant systems. In Proc. NSDI, Apr. 2007.
J. Li, M. N. Krohn, D. Maziéres, and D. Shasha. Secure untrusted
data repository (SUNDR). In Proc. OSDI, Dec. 2004.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
http: //code.
[25]
[26]
[27]
[28]
[29]
[30]
[31]
[32]
[33]
[34]
[35]
[36]
[37]
[38]
[39]
[40]
[41]
[42]
[43]
[44]
[45]
[46]
[47]
[48]
P. Mahajan, S. Setty, S. Lee, A. Clement, L. Alvisi, M. Dahlin, and
M. Walfish. Depot: Cloud storage with minimal trust. In Proc.
OSDI, Oct. 2010.
D. Malkhi and M. Reiter. Byzantine quorum systems. In Proc.
STOC, May 1997.
D. Maziéres and D. Shasha. Building secure file systems out of
byzantine storage. In Proc. PODC, July 2002.
Mozilla Project. | LiveConnect. https://developer.
mozilla.org/en/LiveConnect, 2010.
Mozilla Project. Network security services for Java (JSS). https:
//developer.mozilla.org/En/JSS, 2010.
D. A. Nichols, P. Curtis, M. Dixon, and J. Lamping. High-latency,
low-bandwidth windowing in the Jupiter collaboration system. In
Proc. UIST, Nov. 1995.
A. Oprea and M. K. Reiter. On consistency of encrypted files. In
Proc. Symposium on Distributed Computing (DISC), Sept. 2006.
A. Prakash and M. Knister. A framework for undoing actions in
collaborative systems. ACM Trans. Computer-Human Interaction,
4(1):295-330, Dec. 1994.
M. Ressel, D. Nitsche-Ruhland, and R. Gunzenhauser. An integrat-
ing, transformation-oriented approach to concurrency control and
undo in group editors. In Proc. CSCW, Nov. 1996.
A. Shraer, C. Cachin, A. Cidon, I. Keidar, Y. Michalevsky, and
D. Shaket. Venus: Verification for untrusted cloud storage. In Proc.
ACM CCSW, Oct. 2010.
A. Singh, P. Fonseca, P. Kuznetsov, R. Rodrigues, and P. Maniatis.
Zeno: eventually consistent byzantine-fault tolerance. In Proc.
NSDI, Apr. 2009.
M. Stonebraker. The case for shared nothing. [EEE Database
Engineering Bulletin, 9(1):4—9, 1986.
S. Strohschein. GWTEventService. http://code.google.
com/p/gwteventservice/, 2010.
M. Suleiman, M. Cart, and J. Ferrié. Serialization of concurrent
operations in distributed collaborative environment. In Proc. Conf.
Supporting Group Work (GROUP), Nov. 1997.
C. Sun and C. Ellis. Operational transformation in real-time group
editors: issues, algorithms, and achievements. In Proc. CSCW,
Nov. 1998.
C. Sun, X. Jia, Y. Yang, and Y. Zhang. A generic operation transfor-
mation schema for consistency maintenance in realtime cooperative
editing systems. In Proc. Conf. Supporting Group Work (GROUP),
Nov. 1997.
C. Sun, X. Jia, Y. Zhang, Y. Yang, and D. Chen. Achieving
convergence, causality preservation, and intention preservation
in real-time cooperative editing systems. ACM Trans. Computer-
Human Interaction, 5(1):64—108, 1998.
D. Sun, S. Xia, C. Sun, and D. Chen. Operational transformation
for collaborative word processing. In Proc. CSCW, Nov. 2004.
D. Terry, M. Theimer, K. Petersen, A. Demers, M. Spreitzer, and
C. Hauser. Managing update conflicts in Bayou, a weakly con-
nected replicated storage system. In Proc. SOSP, Dec. 1995.
D. Wang and A. Mah. Google wave operational transforma-
tion. http: //www.waveprotocol.org/whitepapers/
operational-transform, Apr. 2010.
B. Wester, J. Cowling, E. B. Nightingale, P. M. Chen, J. Flinn, and
B. Liskov. Tolerating latency in replicated state machines through
client speculation. In Proc. NSDI, Apr. 2009.
D. Wheeler. SLOCCount. http: //www.dwheeler.com/
sloccount /, 2010.
T. D. Wu. The secure remote password protocol. In Proc. NDSS,
Mar. 1998.
J. Yin, J.-P. Martin, A. Venkataramani, L. Alvisi, and M. Dahlin.
Separating agreement from execution for Byzantine fault tolerant
services. In Proc. SOSP, Oct. 2003.
USENIX Association
USENIX Association
Onix: A Distributed Control Platform for Large-scale Production Networks
Teemu Koponen*, Martin Casado*, Natasha Gude*, Jeremy Stribling*, Leon Poutievski',
Min Zhut, Rajiv Ramanathan', Yuichiro Iwata‘, Hiroaki Inoue?, Takayuki Hamat, Scott Shenker’
Abstract
Computer networks lack a general control paradigm,
as traditional networks do not provide any network-
wide management abstractions. As a result, each new
function (such as routing) must provide its own state
distribution, element discovery, and failure recovery
mechanisms. We believe this lack of a common control
platform has significantly hindered the development of
flexible, reliable and feature-rich network control planes.
To address this, we present Onix, a platform on top of
which a network control plane can be implemented as a
distributed system. Control planes written within Onix
operate on a global view of the network, and use basic
state distribution primitives provided by the platform.
Thus Onix provides a general API for control plane
implementations, while allowing them to make their own
trade-offs among consistency, durability, and scalability.
1 Introduction
Network technology has improved dramatically over
the years with line speeds, port densities, and perfor-
mance/price ratios all increasing rapidly. However,
network control plane mechanisms have advanced at a
much slower pace; for example, it takes several years
to fully design, and even longer to widely deploy, a
new network control protocol.'! In recent years, as
new control requirements have arisen (e.g., greater scale,
increased security, migration of VMs), the inadequacies
of our current network control mechanisms have become
especially problematic. In response, there is a growing
movement, driven by both industry and academia, towards
a control paradigm in which the control plane is decoupled
from the forwarding plane and built as a distributed
system.”
In this model, a network-wide control platform, run-
ning on one or more servers in the network, oversees a
set of simple switches. The control platform handles state
distribution — collecting information from the switches
*Nicira Networks
+ Google
'NEC
8International Computer Science Institute (ICSI) & UC Berkeley
'See, for example, TRILL [32], a recent success story which has
been in the design and specification phase for over 6 years.
?The industrial efforts in this area are typically being undertaken by
entities that operate large networks, not by the incumbent networking
equipment vendors themselves.
and distributing the appropriate control state to them, as
well as coordinating the state among the various platform
servers — and provides a programmatic interface upon
which developers can build a wide variety of management
applications. (The term “management application” refers
to the control logic needed to implement management
features such as routing and access control.)* For the
purposes of this paper, we refer to this paradigm for
network control as Software-Defined Networking (SDN).
This is in contrast to the traditional network control
model in which state distribution is limited to link and
reachability information and the distribution model is
fixed. Today a new network control function (e.g.,
scalable routing of flat intra-domain addresses [21])
requires its own distributed protocol, which involves first
solving a hard, low-level design problem and then later
overcoming the difficulty of deploying this design on
switches. As a result, networking gear today supports
a baroque collection of control protocols with differing
scalability and convergence properties. On the other hand,
with SDN, a new control function requires writing control
logic on top of the control platform’s higher-level API; the
difficulties of implementing the distribution mechanisms
and deploying them on switches are taken care of by the
platform. Thus, not only is the work to implement a
new control function reduced, but the platform provides
a unified framework for understanding the scaling and
performance properties of the system.
Said another way, the essence of the SDN philosophy
is that basic primitives for state distribution should be
implemented once in the control platform rather than
separately for individual control tasks, and should use
well-known and general-purpose techniques from the dis-
tributed systems literature rather than the more specialized
algorithms found in routing protocols and other network
control mechanisms. The SDN paradigm allows network
system implementors to use a single control platform
to implement a range of control functions (e.g., routing,
traffic engineering, access control, VM migration) over a
spectrum of control granularities (from individual flows
to large traffic aggregates) in a variety of contexts (e.g.,
enterprises, datacenters, WANs).
3Just to be clear, we only imagine a single “application” being used
in any particular deployment; this application might address several
issues, such as routing and access control, but the control platform
is not designed to allow multiple applications to control the network
simultaneously (unless the network is “physically sliced’ [28]).
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI’10) 351
352
Because the control platform simplifies the duties of
both switches (which are controlled by the platform) and
the control logic (which is implemented on top of the
platform) while allowing great generality of function,
the control platform is the crucial enabler of the SDN
paradigm. The most important challenges in building a
production-quality control platform are:
e@ Generality: The control platform’s API must allow
management applications to deliver a wide range of
functionality in a variety of contexts.
e Scalability: Because networks (particularly in the
datacenter) are growing rapidly, any scaling limita-
tions should be due to the inherent problems of state
management, not the implementation of the control
platform.
e Reliability: The control platform must handle equip-
ment (and other) failures gracefully.
e Simplicity: The control platform should simplify the
task of building management applications.
e Control plane performance: The control platform
should not introduce significant additional control
plane latencies or otherwise impede management
applications (note that forwarding path latencies
are unaffected by SDN). However, the requirement
here is for adequate control-plane performance, not
optimal performance. When faced with a tradeoff
between generality and control plane performance,
we try to optimize the former while satisficing the
latter.*
While a number of systems following the basic
paradigm of SDN have been proposed, to date there has
been little published work on how to build a network
control platform satisfying all of these requirements.
To fill this void, in this paper we describe the design
and implementation of such a control platform called
Onix (Sections 2-5). While we do not yet have extensive
deployment experience with Onix, we have implemented
several management applications which are undergoing
production beta trials for commercial deployment. We
discuss these and other use cases in Section 6, and present
some performance measures of the platform itself in
Section 7.
Onix did not arise de novo, but instead derives from
a long history of related work, most notably the line
“There might be settings where optimizing control plane
performance is crucial. For example, if one cannot use backup paths for
improved reliability, one can only rely on a fine-tuned routing protocol.
In such settings one might not use a general-purpose control platform,
but instead adopt a more specialized approach. We consider such settings
increasingly uncommon.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
Server 1 Server N
Network Control Logic
Network Control Logic
Switch Import / Export
Management Connectivity Network Infrastructure
Managed Physical Network Infrastructure
et} ——}—_} —} —_+-—|
Ue 0 ame sens anne Sune Snes nee
<
Figure 1: There are four components in an Onix controlled
network: managed physical infrastructure, connectivity
infrastructure, Onix, and the control logic implemented by the
management application. This figure depicts two Onix instances
coordinating and sharing (via the dashed arrow) their views of
the underlying network state, and offering the control logic a
read/write interface to that state. Section 2.2 describes the NIB.
of research that started with the 4D project [15] and
continued with RCP [3], SANE [6], Ethane [5] and
NOX [16] (see [4,23] for other related work). While all of
these were steps towards shielding protocol design from
low-level details, only NOX could be considered a control
platform offering a general-purpose API.> However, NOX
did not adequately address reliability, nor did it give
the application designer enough flexibility to achieve
scalability.
The primary contributions of Onix over existing work
are thus twofold. First, Onix exposes a far more general
API than previous systems. As we describe in Section 6,
projects being built on Onix are targeting environments
as diverse as the WAN, the public cloud, and the
enterprise data center. Second, Onix provides flexible
distribution primitives (such as DHT storage and group
membership) allowing application designers to implement
control applications without re-inventing distribution
mechanisms, and while retaining the flexibility to make
performance/scalability trade-offs as dictated by the
application requirements.
2 Design
Understanding how Onix realizes a production-quality
control platform requires discussing two aspects of its
design: the context in which it fits into the network, and
the API it provides to application designers.
2.1 Components
There are four components in a network controlled by
Onix, and they have very distinct roles (see Figure 1).
e Physical infrastructure: This includes network
switches and routers, as well as any other network
elements (such as load balancers) that support
an interface allowing Onix to read and write the
5Only a brief sketch of NOX has been published; in some ways,
this paper can be considered the first in-depth discussion of a NOX-like
design, albeit in a second-generation form.
USENIX Association
USENIX Association
state controlling the element’s behavior (such as
forwarding table entries). These network elements
need not run any software other than that required
to support this interface and (as described in the
following bullet) achieve basic connectivity.
e Connectivity infrastructure: The communication
between the physical networking gear and Onix (the
“control traffic’”’) transits the connectivity infrastruc-
ture. This control channel may be implemented
either in-band (in which the control traffic shares
the same forwarding elements as the data traffic on
the network), or out-of-band (in which a separate
physical network is used to handle the control
traffic). The connectivity infrastructure must sup-
port bidirectional communication between the Onix
instances and the switches, and optionally supports
convergence on link failure. Standard routing
protocols (such as IS-IS or OSPF) are suitable for
building and maintaining forwarding state in the
connectivity infrastructure.
e Onix: Onix is a distributed system which runs on
a cluster of one or more physical servers, each of
which may run multiple Onix instances. As the
control platform, Onix is responsible for giving
the control logic programmatic access to the net-
work (both reading and writing network state). In
order to scale to very large networks (millions of
ports) and to provide the requisite resilience for
production deployments, an Onix instance is also
responsible for disseminating network state to other
instances within the cluster.
e Control logic: The network control logic is imple-
mented on top of Onix’s API. This control logic
determines the desired network behavior; Onix
merely provides the primitives needed to access the
appropriate network state.
These are the four basic components of an SDN-
based network. Before delving into the design of Onix,
we should clarify our intended range of applicability.
We assume that the physical infrastructure can forward
packets much faster (typically by two or more orders of
magnitude) than Onix (or any general control platform)
can process them; thus, we do not envision using Onix to
implement management functions that require the control
logic to know about per-packet (or other rapid) changes
in network state.
2.2. The Onix API
The principal contribution of Onix is defining a useful and
general API for network control that allows for the de-
velopment of scalable applications. Building on previous
work [16], we designed Onix’s API around a view of the
physical network, allowing control applications to read
and write state to any element in the network. Our API
is therefore data-centric, providing methods for keeping
state consistent between the in-network elements and the
control application (running on multiple Onix instances).
More specifically, Onix’s API consists of a data model
that represents the network infrastructure, with each
network element corresponding to one or more data
objects. The control logic can: read the current state
associated with that object; alter the network state by
operating on these objects; and register for notifications
of state changes to these objects. In addition, since
Onix must support a wide range of control scenarios,
the platform allows the control logic to customize (in a
way we describe later) the data model and have control
over the placement and consistency of each component
of the network state.
The copy of the network state tracked by Onix is stored
in a data structure we call the Network Information Base
(NIB), which we view as roughly analogous to the Rout-
ing Information Base (RIB) used by IP routers. However,
rather than just storing prefixes to destinations, the NIB is
a graph of all network entities within a network topology.
The NIB is both the heart of the Onix control model and
the basis for Onix’s distribution model. Network control
applications are implemented by reading and writing
to the NIB (for example modifying forwarding state or
accessing port counters), and Onix provides scalability
and resilience by replicating and distributing the NIB
between multiple running instances (as configured by the
application).
While Onix handles the replication and distribution of
NIB data, it relies on application-specific logic to both
detect and provide conflict resolution of network state as it
is exchanged between Onix instances, as well as between
an Onix instance and a network element. The control
logic may also dictate the consistency guarantees for state
disseminated between Onix instances using distributed
locking and consensus algorithms.
In order to simplify the discussion, we assume that
the NIB only contains physical entities in the network.
However, in practice it can easily be extended to support
logical elements (such as tunnels).
2.3. Network Information Base Details
At its most generic level, the NIB holds a collection of
network entities, each of which holds a set of key-value
pairs and is identified by a flat, 128-bit, global identifier.
These network entities are the base structure from which
all types are derived. Onix supports stronger typing
through typed entities, representing different network
elements (or their subparts). Typed entities then contain
a predefined set of attributes (using the key-value pairs)
and methods to perform operations over those attributes.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) 353
354
Forwarding [1 _n} Forwarding ,
vos Engine 7] Table Link
________]
we
1 n
Network Node + --> Port
Figure 2: The default network entity classes provided by
Onix’s API. Solid lines represent inheritance, while dashed lines
correspond to referential relation between entity instances. The
numbers on the dashed lines show the quantitative mapping
relationship (e.g., one Link maps to two Ports, and two
Ports can map to the same Link). Nodes, ports and links
constitute the network topology. All entity classes inherit the
same base class providing generic key-value pair access.
For example, there is a Port entity class that can
belong to a list of ports in a Node entity. Figure 2
illustrates the default set of typed entities Onix provides —
all typed entities have a common base class limited to
generic key-value pair access. The type-set within Onix is
not fixed and applications can subclass these basic classes
to extend Onix’s data model as needed.®
The NIB provides multiple methods for the control
logic to gain access to network entities. It maintains an
index of all of its entities based on the entity identifier,
allowing for direct querying of a specific entity. It also
supports registration for notifications on state changes
or the addition/deletion of an entity. Applications can
further extend the querying capabilities by listening for
notifications of entity arrivals and maintaining their own
indices.
The control logic for a typical application is therefore
fairly straightforward. It will register to be notified on
some state change (e.g., the addition of new switches and
ports), and once the notification fires, it will manipulate
the network state by modifying the key-value pairs of the
affected entities.
The NIB provides neither fine-grained nor distributed
locking mechanisms, but rather a mechanism to request
and release exclusive access to the NIB data structure
of the local instance. While the application is given the
guarantee that no other thread is updating the NIB within
the same controller instance, it is not guaranteed the
state (or related state) remains untouched by other Onix
instances or network elements. For such coordination,
it must use mechanisms implemented externally to the
NIB. We describe this in more detail in Section 4; for now,
we assume this coordination is mostly static and requires
control logic involvement during failure conditions.
All NIB operations are asynchronous, meaning that
updating a network entity only guarantees that the update
message will eventually be sent to the corresponding
®Subclassing also enables control over how the key-value pairs are
stored within the entity. Control logics may prefer different trade-offs
between memory and CPU usage.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
Category Purpose
Find entities.
Create and remove entities.
Inspect and modify entities.
Query
Create, destroy
Access attributes
Notifications Receive updates about changes.
Synchronize Wait for updates being exported to
network elements and controllers.
Configuration Configure how state is imported
to and exported from the NIB.
Pull Ask for entities to be imported
on-demand.
Table 1: Functions provided by the Onix NIB API.
network element and/or other Onix instances — no
ordering or latency guarantees are given. While this
has the potential to simplify the control logic and make
multiple modifications more efficient, often it is useful to
know when an update has successfully completed. For
instance, to minimize disruption to network traffic, the
application may require the updating of forwarding state
on multiple switches to happen in a particular order (to
minimize, for example, packet drops). For this purpose,
the API provides a synchronization primitive: if called
for an entity, the control logic will receive a callback once
the state has been pushed. After receiving the callback,
the control logic may then inspect the contents of the NIB
and verify that the state is as expected before proceeding.
We note that if the control logic implements distributed
coordination, race-conditions in state updates will either
not exist or will be transient in nature.
An application may also only rely on NIB notifications
to react to failures in modifications as they would any
other network state changes. Table | lists available NIB-
manipulation methods.
3 Scaling and Reliability
To be a viable alternative to the traditional network
architecture, Onix must meet the scalability and reliability
requirements of today’s (and tomorrow’s) production net-
works. Because the NIB is the focal point for the system
state and events, its use largely dictates the scalability and
reliability properties of the system. For example, as the
number of elements in the network increases, a NIB that
is not distributed could exhaust system memory. Or, the
number of network events (generated by the NIB) or work
required to manage them could grow to saturate the CPU
of a single Onix instance.’
This and the following section describe the NIB
distribution framework that enables Onix to scale to very
7In one of our upcoming deployments, if a single-instance
application took one second to analyze the statistics of a single Port
and compute a result (e.g., for billing purposes), that application would
take two months to process all Ports in the NIB.
USENIX Association
USENIX Association
large networks, and to handle network and controller
failure.
3.1 Scalability
Onix supports three strategies that can used to improve
scaling. First, it allows control applications to partition
the workload so that adding instances reduces work
without merely replicating it. Second, Onix allows for
aggregation in which the network managed by a cluster
of Onix nodes appears as a single node in a separate
cluster’s NIB. This allows for federated and hierarchical
structuring of Onix clusters, thus reducing the total
amount of information required within a single Onix
cluster. Finally, Onix provides applications with control
over the consistency and durability of the network state.
In more detail:
e Partitioning. The network control logic may config-
ure Onix so that a particular controller instance keeps
only a subset of the NIB in memory and up-to-date.
Further, one Onix instance may have connections to
a subset of the network elements, and subsequently,
can have fewer events originating from the elements
to process.
e Aggregation. In a multi-Onix setup, one instance of
Onix can expose a subset of the elements in its NIB
as an aggregate element to another Onix instance.
This is typically used to expose less fidelity to upper
tiers in a hierarchy of Onix controllers. For example,
in a large campus network, each building might
be managed by an Onix controller (or controller
cluster) which exposes all of the network elements
in that building as a single aggregate node to a global
Onix instance which performs campus-wide traffic
engineering. This is similar in spirit to global control
management paradigms in ATM networks [27].
e Consistency and durability. The control logic
dictates the consistency requirements for the network
state it manages. This is done by implementing any
of the required distributed locking and consistency
algorithms for state requiring strong consistency,
and providing conflict detection and resolution for
state not guaranteed to be consistent by use of these
algorithms. By default, Onix provides two data
stores that an application can use for state with differ-
ing preferences for durability and consistency. For
state applications that favor durability and stronger
consistency, Onix offers a replicated transactional
database and, for volatile state that is more tolerant
of inconsistencies, a memory-based one-hop DHT.
We return to these data stores in Section 4.
The above scalability mechanisms can be used to
manage networks too large to be controlled by a single
Onix instance. To demonstrate this, we will use a
running example: an application that can establish paths
between switches in a managed topology, with the goal
of establishing complete routes through the network.
Partition. We assume a network with a modest number
of switches that can be easily handled by a single Onix
instance. However, the number and size of all forwarding
state entries on the network exceeds the memory resources
of a single physical server.
To handle such a scenario, the control logic can repli-
cate all switch state, but it must partition the forwarding
state and assign each partition to a unique Onix instance
responsible for managing that state. The method of
partitioning is unimportant as long as it creates relatively
consistent chunks.
The control logic can record the switch and link
inventory in the fully-replicated, durable state shared
by all Onix instances, and it can coordinate updates
using mechanisms provided by the platform. However,
information that is more volatile, such as link utilization
levels, can be stored in the DHT. Each controller can
use the NIB’s representation of the complete physical
topology (from the replicated database), coupled with
link utilization data (from the DHT), to configure the
forwarding state necessary to ensure paths meeting the
deployment’s requirements throughout the network.
The resulting distribution strategy closely resembles the
use of head-end routers in MPLS [24] to manage tunnels.
However, instead of a DHT, MPLS uses intra-domain
routing protocols to disseminate the link utilization
information.
Aggregate. As our example network grows, partition-
ing the path management no longer suffices. We assume
that the Onix instances are still capable of holding the
full NIB, but the control logic cannot keep up with the
number of network events and thus saturates the CPU.
This scenario follows from our experience in which CPU
is commonly the limiting factor for control applications.
To shield remote instances from high-rates of updates,
the application can aggregate a topology as a single
logical node, and use that as the unit of event dissem-
ination between instances. For example, the topology
can be divided into logical areas, each managed by a
distinct Onix instance. Onix instances external to an
area would know the exact physical topology within the
area, but would retrieve only topologically-aggregated
link-utilization information from the DHT (originally
generated by instances within that area).
This use of topological aggregation is similar to ATM
PNNI [27], in which the internals of network areas are
aggregated into single logical nodes when exposed to
neighboring routers. The difference is that the Onix
instances and switches still have full connectivity between
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) = 355
356
them and it is assumed that the latency between any
element (between the switches and Onix instances or
between Onix instances) is not a problem.
Partition further. At some point, the number of el-
ements within a control domain will overwhelm the
capacity of a single Onix instance. However, due to
relatively slow change rates of the physical network, it is
still possible to maintain a distributed view of the network
graph (the NIB).
Applications can still rely on aggregating link utiliza-
tion information, but in a partitioned NIB scheme, they
would use the inter-Onix state distribution mechanisms to
mediate requests to switches in remote areas; this can be
done by using NIB attributes as a remote communication
channel. The “request” and “response” are relayed
between the areas using the DHT. Because this transfer
might happen via a third Onix instance, any application
that needs faster response times may configure DHT key
ranges for areas and use DHT keys such that for the
modified entity its attributes are stored within the proper
area.
It is possible for this approach to scale to wide-area
deployment scenarious. For example, each partition
could represent a large network area, and each network
is exposed as an aggregate node to a cluster of Onix
instances that make global routing decisions over the
aggregate nodes. Thus, each partition makes local
routing decisions, and the cluster makes routing decisions
between these partitions (abstracting each as a single
logical node). The state distribution requirements for
this approach would be almost identical to hierarchical
MPLS.
Inter-domain aggregation. Once the controlled net-
work spans two separate ASes, sharing full topology
information among the Onix instances becomes infeasible
due to privacy reasons and the control logic designer
needs to adapt the design again to changed requirements.
The platform does not dictate how the ASes would peer,
but at a high-level they would have two requirements
to fulfill: a) sharing their topologies at some level of
detail (while preserving privacy) with their peers, and b)
establishing paths for each other proactively (according
to a peering contract) or on-demand, and exchanging their
ingress information. For both requirements, there are
proposals in academia [13] and industry deployments [12]
that applications could implement to arrange peering
between Onix instances in adjacent ASes.
3.2. Reliability
Control applications on Onix must handle four types
of network failures: forwarding element failures, link
failures, Onix instance failures, and failures in connectiv-
ity between network elements and Onix instances (and
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
between the Onix instances themselves). This section
discusses each in turn.
Network element and link failures. Modern control
planes already handle network element and link failures,
and control logic built on Onix can use the same
mechanisms. If a network element or link fails, the
control logic has to steer traffic around the failures. The
dissemination times of the failures through the network
together with the re-computation of the forwarding tables
define the minimum time for reacting to the failures.
Given increasingly stringent requirements convergence
times, it may be preferrable that convergence be handled
partially by backup paths with fast failover mechanisms
in the network element.
Onix failures. To handle an Onix instance failure, the
control logic has two options: running instances can
detect a failed node and take over the responsibilities
of the failed instance quickly, or more than one instance
can simultaneously manage each network element.
Onix provides coordination facilities (discussed in
Section 4) for detecting and reacting to instance failures.
For the simultaneous management of a network element
by more than one Onix instance, the control logic has
to handle lost update race conditions when writing to
network state. To help, Onix provides hooks that appli-
cations can use to determine whether conflicting changes
made by other instances to the network element can be
overridden. Provided the control logic computes the same
network element state in a deterministic fashion at each
Onix instance, i.e., every Onix instance implements the
same algorithm, the state can remain inconsistent only
transiently. At the high-level, this approach is similar to
the reliability mechanisms of RCP [3], in which multiple
centralized controllers push updates over iBGP to edge
routers.
Connectivity infrastructure failures. Onix state dis-
tribution mechanisms decouple themselves from the
underlying topology. Therefore, they require connectivity
to recover from failures, both between network elements
and Onix instances as well as between Onix instances.
There are a number of methods for establishing this
connectivity. We describe some of the more common
deployment scenarios below.
It is not unusual for an operational network to have a
dedicated physical network or VLAN for management.
This is common, for example, in large datacenter build-
outs or hosting environments. In such environments,
Onix can use the management network for control traffic,
isolating it from forwarding plane disruptions. Under
this deployment model, the control network uses standard
networking gear and thus any disruption to the control
network is handled with traditional protocols (e.g., OSPF
or spanning tree).
USENIX Association
USENIX Association
Even if the environment does not provide a separate
control network, the physical network topology is typ-
ically known to Onix. Therefore, it is possible for the
control logic to populate network elements with static
forwarding state to establish connecitivty between Onix
and the switches. To guarantee connectivity in presence
of failures, source routing can be combined with multi-
pathing (also implemented below Onix): source routing
packets over multiple paths can guarantee extremely
reliable connectivity to the managed network elements,
as well as between Onix instances.
4 Distributing the NIB
This section describes how Onix distributes its Network
Information Base and the consistency semantics an
application can expect from it.
4.1 Overview
Onix’s support for state distribution mechanisms was
guided by two observations on network management ap-
plications. First, applications have differing requirements
on scalability, frequency of updates on shared space,
and durability. For example network policy declarations
change slowly and have stringent durability requirements.
Conversely, logic using link load information relies on
rapidly-changing network state that is more transient
in nature (and thus does not have the same durability
requirements).
Second, distinct applications often have different
requirements for the consistency of the network state
they manage. Link state information and network
policy configurations are extreme examples: transiently-
inconsistent status flags of adjacent links are easier for an
application to resolve than an inconsistency in network-
wide policy declaration. In the latter case, a human may
be needed to perform the resolution correctly.
Onix supports an application’s ability to choose be-
tween update speeds and durability by providing two sep-
arate mechanisms for distributing network state updates
between Onix instances: one designed for high update
rates with guaranteed availability, and one designed
with durability and consistency in mind. Following
the example of many distributed storage systems that
allow applications to make performance/scalability trade-
offs [2, 8, 29, 31], Onix makes application designers
responsible for explicitly determining their preferred
mechanism for any given state in the NIB — they can
also opt to use the NIB solely as storage for local state.
Furthermore, Onix can support arbitrary storage systems
if applications write their own import and export modules,
which transfer data into the NIB from storage systems
and out of the NIB to storage systems respectively.
In solving the applications’ preference for differing
consistency requirements, Onix relies on their help: it
expects the applications to use the provided coordination
facilities [19] to implement distributed locking or consen-
sus protocols as needed. The platform also expects the
applications to provide the implementation for handling
any inconsistencies arising between updates, if they are
not using strict data consistency. While applications are
given the responsibility to implement the inconsistency
handling, Onix provides a programmatic framework to
assist the applications in doing so.
Thus, application designers are free to determine
the trade-off between potentially simplified application
architectures (promoting consistency and durability) and
more efficient operations (with the cost of increased
complexity). We now discuss the state distribution
between Onix instances in more detail, as well as how
Onix integrates network elements and their state into these
distribution mechanisms.
4.2 State Distribution Between Onix Instances
Onix uses different mechanisms to keep state consistent
between Onix instances and between Onix and the
network forwarding elements. The reasons for this are
twofold. First, switches generally have low-powered
management CPUs and limited RAM. Therefore, the
protocol should be lightweight and primarily for con-
sistency of forwarding state. Conversely, Onix instances
can run on high powered general compute platforms and
don’t have such limitations. Secondly, the requirements
for managing switch state are much narrower and better
defined than that needed by any given application.
Onix implements a transactional persistent database
backed by a replicated state machine for disseminating
all state updates requiring durability and simplified
consistency management. The replicated database comes
with severe performance limitations, and therefore it
is intended to serve only as a reliable dissemination
mechanism for slowly changing network state. The
transactional database provides a flexible SQL-based
querying API together with triggers and rich data models
for applications to use directly, as necessary.
To integrate the replicated database with the NIB,
Onix includes import/export modules that interact with
the database. These components load and store entity
declarations and their attributes from/to the transactional
database. Applications can easily group NIB modifica-
tions together into a single transaction to be exported to
the database. When the import module receives a trigger
invocation from the database about changed database
contents, it applies the changes to the NIB.
For network state needing high update rates and avail-
ability, Onix provides a one-hop, eventually-consistent,
memory-only DHT (similar to Dynamo [9]), relaxing
the consistency and durability guarantees provided by
the replicated database. In addition to the common
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) 357
358
get/put API, the DHT provides soft-state triggers: the
application can register to receive a callback when a
particular value gets updated, after which the trigger must
be reinstalled. False positives are allowed to simplify
the implementation of the DHT replication mechanism.
The DHT implementation manages its membership state
and assigns key-range responsibilities using the same
coordination mechanisms provided to applications.
Updates to the DHT by multiple Onix instances can
lead to state inconsistencies. For instance, while using
triggers, the application must be carefully prepared for any
race conditions that could occur due to multiple writers
and callback delays. Also, the introduction of a second
storage system may result in inconsistencies in the NIB.
In such cases, the Onix DHT returns multiple values for
a given key, and it is up to the applications to provide
conflict resolution, or avoid these conditions by using
distributed coordination mechanisms.
4.3 Network Element State Management
The Onix design does not dictate a particular protocol for
managing network element forwarding state. Rather, the
primary interface to the application is the NIB, and any
suitable protocol supported by the elements in the network
can be used under the covers to keep the NIB entities in
sync with the actual network state. In this section we
describe the network element state management protocols
currently supported by Onix.
OpenFlow [23] provides a performance-optimized
channel to the switches for managing forwarding tables
and quickly learning port status changes (which may have
an impact on reachability within the network). Onix turns
OpenFlow events and operations into state that it stores in
the NIB entities. For instance, when an application adds
a flow entry toa ForwardingTabl1e entity in the NIB,
the OpenFlow export component will translate that into
an OpenFlow operation that adds the entry to the switch
TCAM. Similarly, the TCAM entries are accessible to the
application in the contents of the ForwardingTable
entity.
For managing and accessing general switch configu-
ration and status information, an Onix instance can opt
to connect to a switch over a configuration database pro-
tocol (such as the one supported by Open vSwitch [26]).
Typically this database interface exposes the switch
internals that OpenFlow does not. For Onix, the protocol
provides a mechanism to receive a stream of switch state
updates, as well as to push changes to the switch state.
The low-level semantics of the protocol closely resembles
the transactional database (used between controllers)
discussed above, but instead of requiring full SQL support
from the switches, the database interface has a more
restricted query language that does not provide joins.
Similarly to the integration with OpenFlow, Onix
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
provides convenient, data-oriented access to the switch
configuration state by mapping the switch database
contents to NIB entities that can be read and modified
by the applications. For example, by creating and
attaching Port entities with proper attributes to a
ForwardingEngine entity (which corresponds to a
single switch datapath), applications can configure new
tunnel endpoints without knowing that this translates to
an update transaction sent to the corresponding switch.
4.4 Consistency and Coordination
The NIB is the central integration point for multiple data
sources (other Onix instances as well as connected net-
work elements); that is, the state distribution mechanisms
do not interact directly with each other, but rather they
import and export state into the NIB. To support multiple
applications with possibly very different scalability and
reliability requirements, Onix requires the applications
to declare what data should be imported to and exported
from a particular source. Applications do this through the
configuration of import and export modules.
The NIB integrates the data sources without requiring
strong consistency, and as a result, the state updates to
be imported into NIB may be inconsistent either due
to the inconsistency of state within an individual data
source (DHT) or due to inconsistencies between data
sources. To this end, Onix expects the applications to
register inconsistency resolution logic with the platform.
Applications have two means to do so. First, in Onix,
entities are C++ classes that the application may extend,
and thus, applications are expected simply to use in-
heritance to embed referential inconsistency detection
logic into entities so that applications are not exposed to
inconsistent state.* Second, the plugins the applications
pass to the import/export components implement conflict
resolution logic, allowing the import modules to know
how to resolve situations where both the local NIB and
the data source have changes for the same state.
For example, consider a new Node N, imported into
the NIB from the replicated database. If N contains
a reference in its list of ports to Port P that has not
yet been imported (because they are retrieved from the
network elements, not from the replicated database), the
application might prefer that NV not expose a reference
to P to the control logic until P has been imported.
Furthermore, if the application is using the DHT to
store statistics about the number of packets forwarded
by N, it is possible for the import module of an
Onix instance to retrieve two different values for this
number from the DHT (e.g., due to rebalancing of
controllers’ responsibilities within a cluster, resulting in
two controllers transiently updating the same value). The
8 Any inconsistent changes remain pending within the NIB until they
can be applied or applications deem it invalid for good.
USENIX Association
USENIX Association
application’s conflict resolution logic must reconcile these
values, storing only one into the NIB and back out to the
DHT.
This leaves the application with a consistent topology
data model. However, the application still needs to react to
Onix instance failures and use the provided coordination
mechanisms to determine which instances are responsible
for different portions of the NIB. As these responsibilities
shift within the cluster, the application must instruct the
corresponding import and export modules to adjust their
behaviors.
For coordination, Onix embeds Zookeeper [19] and
provides applications with an object-oriented API to its
filesystem-like hierarchical namespace, convenient for
realizing distributed algorithms for consensus, group
membership, and failure detection. While some appli-
cations may prefer to use Zookeeper’s services directly
to store persistent configuration state instead of the trans-
actional database, for most the object size limitations of
Zookeeper and convenience of accessing the configuration
state directly through the NIB are a reason to favor the
transactional database.
5 Implementation
Onix consists of roughly 150,000 lines of C++ and
integrates a number of third party libraries. At its simplest,
Onix is a harness which contains logic for communicating
with the network elements, aggregating that information
into the NIB, and providing a framework in which appli-
cation programmers can write a management application.
A single Onix instance can run across multiple pro-
cesses, each implemented using a different programming
language, if necessary. Processes are interconnected using
the same RPC system that Onix instances can use among
themselves, but instead of running over TCP/IP it runs
over local IPC connections. In this model, supporting a
new programming language becomes a matter of writing
a few thousand lines of integration code, typically in the
new language itself. Onix currently supports C++, Python,
and Java.
Independent of the programming language, all soft-
ware modules in Onix are written as loosely-coupled
components, which can be replaced with others without
recompiling Onix as long as the component’s binary
interface remains the same. Components can be loaded
and unloaded dynamically and designers can express
dependencies between components to ensure they are
loaded and unloaded in the proper order.
6 Applications
In this section, we discuss some applications currently
being built on top of Onix. In keeping with the focus of
the paper, we limit the applications discussed to those that
are being developed for production environments. We
believe the range of functionality they cover demonstrates
the generality of the platform. Table 2 lists the ways in
which these applications stress the various Onix features.
Ethane. For enterprise networks, we have built a
network management application similar to Ethane [5] to
enforce network security policies. Using the Flow-based
Management Language (FML) [18] network administra-
tors can declare security policies in a centralized fashion
using high-level names instead of network-level addresses
and identifiers. The application processes the first packet
of every flow obtained from the first hop switch: it tracks
hosts’ current locations, applies the security policies, and
if the flow is approved, sets up the forwarding state for
the flow through the network to the destination host. The
link state of the network is discovered through LLDP
messages sent by Onix instances as each switch connects.
Since the aggregate flow traffic of a large network can
easily exceed the capacity of a single server, large-scale
deployment of our implementation, it requires multiple
Onix instances to partition the flow processing. Further,
having Onix on the flow-setup path makes failover
between multiple instances particularly important.
Partitioning the flow-processing state requires that all
controllers be able to set up paths in the network, end to
end. Therefore, each Onix instance needs to know the
location of all end-points as well as the link state of the
network. However, it is not particularly important that this
information be strongly consistent between controllers.
At worst, a flow is routed to an old location of the host
over a failed link, which is impossible to avoid during
network element failures. It is also unnecessary for
the link state to be persistent, since this information is
obtained dynamically. Therefore, the controllers can
use the DHT for storing link-state, which allows tens
of thousands of updates per second (see Section 7).
Distributed Virtual Switch (DVS). In virtualized en-
terprise network environments, the network edge consists
of virtual, software-based L2 switch appliances within
hypervisors instead of physical network switches [26].
It is not uncommon for virtual deployments (especially
in cloud-hosting providers) to consist of tens of VMs
per server, and to have hundreds, thousands or tens of
thousands of VMs in total. These environments can also
be highly dynamic, such that VMs are added, deleted and
migrated on the fly.
To cope with such environments, the concept of a
distributed virtual switch (DVS) has arisen [33]. A DVS
roughly operates as follows. It provides a logical switch
abstraction over which policies (e.g., policing, QoS,
ACLs) are declared over the logical switch ports. These
ports are bound to virtual machines through integration
with the hypervisor. As the machines come and go and
move around the network, the DVS ensures that the
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) 359
360
Control Logic
Flow Setup
Distribution | Availability | Integration
Ethane -
Distributed virtual switch
Multi-tenant virtualized datacenter
Scale-out carrier-grade IP router
J
v
v v
Vv
Table 2: Aspects of Onix especially stressed by deployed control logic applications.
policies follow the VMs and therefore do not have to
be reconfigured manually; to this end, the DVS integrates
to the host virtualization platform.
Thus, when operating as part of a DVS application,
Onix is not involved in forwarding plane flow setup,
but only invoked when VMs are created, destroyed, or
migrated. Hypervisors are organized as pools consisting
of a reasonably small number of hypervisors and VMs
typically do not migrate across pools; and therefore,
the control logic can easily partition itself according to
these pools. A single Onix instance then handles all the
hypervisors of a single pool. All the switch configuration
state is persisted to the transactional database, whereas all
VM locations are not shared between Onix instances.
If an Onix instance goes down, the network can
still operate. However, VM dynamics will no longer
be allowed. Therefore, high availability in such an
environment is less critical than in the Ethane environment
described previously, in which an Onix crash would
render the network inoperable to new flows. In our DVS
application, for simplicity reasons reliability is achieved
through a cold standby prepared to boot in a failure
condition.
Multi-tenant virtualized data centers. Multi-tenant
environments exacerbate the problems described in the
context of the previous application. The problem state-
ment is similar, however: in addition to handling end-host
dynamics, the network must also enforce both addressing
and resource isolation between tenant networks. Tenant
networks may have, for example, overlapping MAC
or IP addresses, and may run over the same physical
infrastructure.
We have developed an application on top of Onix which
allows the creation of tenant-specific L2 networks. These
networks provide a standard Ethernet service model and
can be configured independently of each other and can
span physical network subnets.
The control logic isolates tenant networks by encap-
sulating tenants’ packets at the edge, before they enter
the physical network, and decapsulating them when they
either enter another hypervisor or are released to the
Internet. For each tenant virtual network, the control logic
establishes tunnels pair-wise between all the hypervisors
running VMs attached to the tenant virtual network. As
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
a result, the number of required tunnels is O(N =), and
thus, with potentially tens of thousands of VMs per tenant
network, the state for just tunnels may grow beyond the
capacity of a single Onix instance, not to mention that the
switch connections can be equally numerous.”
Therefore, the control logic partitions the tenant net-
work so that multiple Onix instances share responsibility
for the network. A single Onix instance manages only a
subset of hypervisors, but publishes the tunnel end-point
information over the DHT so any other instances needing
to set up a tunnel involving one of those hypervisors can
configure the DHT import module to load the relevant
information into the NIB. The tunnels themselves are
stateless, and thus, multiple hypervisors can send traffic
to a single receiving tunnel end-point.
Scale-out carrier-grade IP router. We are currently
considering a design to create a scale-out BGP router us-
ing commodity switching components as the forwarding
plane. This project is still in the design phase, but we
include it here to demonstrate how Onix can be used with
traditional network control logic.
In our design, Onix provides the “glue” between the
physical hardware (a collection of commodity switches)
and the control plane (an open source BGP stack). Onix
is therefore responsible for aggregating the disparate
hardware devices and presenting them to the control logic
as a single forwarding plane, consisting of an L2/L3 table,
and a set of ports. Onix is also responsible for translating
the RIB, as calculated by the BGP stack, into flow entries
across the cluster of commodity switches.
In essence, Onix will provide the logic to build a scale-
out chassis from the switches. The backplane of the
chassis is realized through the use of multiple connections
and multi-pathing between the switches, and individual
switches act as line-cards. If a single switch fails, Onix
will alert the routing stack that the associated ports on the
full chassis have gone offline. However, this should not
affect the other switches within the cluster.
The control traffic from the network (e.g., BGP or
IGP traffic) is forwarded from the switches to Onix,
which annotates it with the correct logical switch port and
forwards it to the routing stack. Because only a handful of
°The VMs of a single tenant are not likely to share physical servers
to avoid fate-sharing in hardware failure conditions.
USENIX Association
USENIX Association
oy
Bi Pesenteeie tease "4 Attribute 4
3 10 Attributes — — -
& 6M Fears: 1000 Attributes -- - +
24M are: ‘
<= es Swe
oO as eS oe oe
2 2M : = NS ei re a a ee
cE i
0 16 32 48 64
Number of listeners
Figure 3: Attribute modification throughput as the number of
listeners attached to the NIB increases.
° 0 Attributes - - - Eee |
g 10 Attributes = ed
a GB 20 Attributes es
7 30 Attributes ------ “eon
= ae
= 1GB aa
E ; eee
S nan ee =. ‘
Number of NIB entities
Figure 4: Memory usage as the number of NIB entities
increases.
switches are used, the memory and processing demands
of this applications are relatively modest. A single Onix
instance with an active failover (on which the hardware
configuration state is persistent) is sufficient for even very
large deployments. This application is discussed in more
detail in [7].
7 Evaluation
In this section, we evaluate Onix in two ways: with
micro-benchmarks, designed to test Onix’s performance
as a general platform, and with end-to-end performance
measurements of an in-development Onix application in
a test environment.
7.1 Scalability Micro-Benchmarks
Single-node performance. We first benchmark three
key scalability-related aspects of a single Onix instance:
throughput of the NIB, memory usage of the NIB, and
bandwidth in the presence of many connections.
The NIB is the focal point of the API, and the
performance of an application will depend on the capacity
the NIB has for processing updates and notifying listeners.
To measure this throughput, we ran a micro-benchmark
where an application repeatedly acquired exclusive access
to the NIB (by its cooperative thread acquiring the CPU),
modified integer attributes of an entity (which triggers
immediate notification of any registered listeners), and
then released NIB access. In this test, none of the listeners
acted on the notifications of NIB changes they received.
Figure 3 contains the results. With only a single attribute
modification, this micro-benchmark essentially becomes
200k
150k
100k
50k
Pkts/s
1 4 16 64 256 512 1k
Number of OpenFlow connections (log)
Figure 5: Number of 64-byte packets forwarded per second by
a single Onix node, as the # of switch connections increases.
a benchmark for our threading library, as acquiring
exclusive access to the NIB translates to a context switch.
As the number of modified attributes between context
switches increases, the effective throughput increases
because the modifications involve only a short, fine-tuned
code path through the NIB to the listeners.
Onix NIB entities provide convenient state access
for the application as well as for import and export
modules. The NIB must thus be able to handle a
large number of entries without excessive memory usage.
Figure 4 displays the results of measuring the total
memory consumption of the C++ process holding the
NIB while varying both network topology size and the
number of attributes per entity. Each attribute in this
test is 16 bytes (on average), with an 8-byte attribute
identifier (plus C++ string overhead); in addition, Onix
uses a map to store attributes (for indexing purposes) that
reserves memory in discrete chunks. A zero-attribute
entity, including the overhead of storing and indexing
it in the NIB, consumes 191 bytes. The results in
Figure 4 suggest a single Onix instance (on a server-
grade machine) can easily handle networks of millions
of entities. As entities include more attributes, their sizes
increase proportionally.
Each Onix instance has to connect to the switches
it manages. To stress this interface, we connected
a (software) switch cloud to a single Onix instance and
ran an application that, after receiving a 64-byte packet
from a random switch, made a forwarding decision
without updating the switch’s forwarding tables. That
is, the application sent the packet back to the switch with
forwarding directions for that packet alone. Because of
the application’s simplicity, the test effectively bench-
marks the performance of our OpenFlow stack, which
has the same code path for both packets and network
events (such as port events). Figure 5 shows the stack
can perform well (forwarding over one hundred thousand
packets per second), with up to roughly one thousand
concurrent connections. We have not yet optimized our
implementation in this regard, and the results highlight a
known limitation of our threading library, which forces
the OpenFlow protocol stack to do more threading context
switches as the number of connections increases. Bumps
in the graph are due to the operating system scheduling
the controller process over multiple CPU cores.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI’10) 361
362
APOKy ne Be ee
80k
40k
Calls/s
1 8 64 512 4k 32k
Total request-response size in bytes (log)
Figure 6: RPC calls per second processed by a single Onix
node, as the size of the RPC request-response pair increases.
Probability
0 0.5 1 1.5 2 2.5
Time from new value put before all instances get it (ms)
Figure 7: A CDF showing the latency of updating a DHT value
at one node, and for that update to be fetched by another node
in a 5-node network.
Multi-node performance. Onix instances use three
mechanisms to cooperate: two state update dissemination
mechanisms (the DHT and the replicated, transactional
database) and the Zookeeper coordination mechanism.
Zookeeper’s performance has been studied elsewhere [19],
so we focus on the DHT and replicated database.
The throughput of our memory-based DHT is effec-
tively limited by the Onix RPC stack. Figure 6 shows
the call throughput between an Onix instance acting as
an RPC client, and another acting as an RPC server, with
the client pipelining requests to compensate for network
latency. The DHT performance can then be seen as the
RPC performance divided by the replication factor. While
a single value update may result in both a notification
call and subsequent get calls from each Onix instance
having an interest in the value, the high RPC throughput
still shows our DHT to be capable of handling very
dynamic network state. For example, if you assume
that an application fully replicates the NIB to 5 Onix
instances, then each NIB update will result in 22 RPC
request-response pairs (2 to store two copies of the data
in the DHT, 2+5 to notify all instances of the update, and
25 for all instances to fetch the new value from both
replicas and reinstall their triggers). Given the results in
Figure 6, this implies that the application, in aggregate,
can handle 24,000 small DHT value updates per second.
In a real deployment this might translate, for example,
to updating a load attribute on 24,000 link entities every
second — a fairly ambitious scale for any physical network
that is controlled by just five Onix instances. Applications
can use aggregation and NIB partitioning to scale further.
Our replicated transactional database is not optimized
for throughput. However, its performance has not yet
become a bottleneck due to the relatively static nature
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
Queries/trans 1 10 20 50
49.7 | 331.9 | 520.1 | 541.7
100
494.4
Queries/s
Table 3: The throughput of Onix’s replicated database.
of the data it stores. Table 3 shows the throughput
for different query batching sizes (1/3 of queries are
INSERTs, and 2/3 are SELECTs) in a 5-node replicated
database. If the application stores its port inventory in the
replicated database, for example, without any batching it
can process 17 port additions and removals per second,
along with about 6.5 queries per second from each node
about the existence of ports (17 + 6.5 * 5 ~ 49.7).
7.2 Reliability Micro-Benchmarks
A primary consideration for production deployments is
reliability in the face of failures. We now consider
the three failure modes a control application needs to
handle: link failures, switch failures, and Onix instance
failures. Finally, we consider the perceived network
communication failure time with an Onix application.
Link and switch failures. Onix instances monitor their
connections to switches using aggressive keepalives.
Similarly, switches monitor their links (and tunnels) using
hardware-based probing (such as 802.lag CFM [1]).
Both of these can be fine-tuned to meet application
requirements.
Once a link or switch failure is reported to the control
application, the latencies involved in disseminating the
failure-related state updates throughout the Onix cluster
become essential; they define the absolute minimum time
the control application will take to react to the failure
throughout the network.
Figure 7 shows the latencies of DHT value propagation
in a 5-node, LAN-connected network. However, once
the controllers are more distant from each other in
the network, the DHT’s pull-based approach begins to
introduce additional latencies compared to the ideal push-
based methods common in distributed network protocols
today. Also, the new value being put to the DHT may
be placed on an Onix instance not on the physical path
between the instance updating the value and the one
interested in the new value. Thus, in the worst case, a
state update may take four times as long as it takes to push
the value (one hop to put the new value, one to notify an
interested Onix instance, and two to get the new value).
In practice, however, this overhead tends not to
impact network performance, because practical avail-
ability requirements for production traffic require the
control application to prepare for switch and link failures
proactively by using backup paths.
Onix instance failures. The application has to detect
failed Onix instances and then reconfigure responsibilities
within the Onix cluster. For this, applications rely on the
USENIX Association
USENIX Association
Probability
: 0 200 400 600 800
Perceived failure latency (ms)
Figure 8: A CDF of the perceived communication disruption
time between two hosts when an intermediate switch fails. These
measurements include the one-second (application-configurable)
keepalive timeout used by Onix. The hosts measure the
disruption time by sending a ping every 10 ms and counting the
number of missed replies.
1000 1200 1400
Zookeeper coordination facilities provided by Onix. As
with its throughput, we refer the reader to a previous
study [19] for details.
Application test. Onix is currently being used by a
number of organizations as the platform for building
commercial applications. While scaling work and testing
is ongoing, applications have managed networks of up to
64 switches with a single Onix instance, and Onix has
been tested in clusters of up to 5 instances.
We now measure the end-to-end failure reaction time of
the multi-tenant virtualized data center application (Sec-
tion 6). The core of the application is a set of tunnels
creating an L2 overlay. If a switch hosting a tunnel fails,
the application must patch up the network quickly to
ensure continued connectivity withing the overlay.
Figure 8 shows how quickly the application can create
new tunnels to reestablish the connectivity between hosts
when a switch hosting a tunnel fails. The measured time
includes the time for Onix to detect the switch failure,
and for the application to decide on a new switch to
host the tunnel, create the new tunnel endpoints, and
update the switch forwarding tables. The figure shows the
median disruption for the host-to-host communication is
1120 ms. Given the configured one-second switch failure
detection time, this suggests it takes Onix 120 ms to repair
the tunnel once the failure has been detected. Although
this application is unoptimized, we believe these results
hold promise that a complete application on Onix can
achieve reactive properties on par with traditional routing
implementations.
8 Related Work
As mentioned in Section 1, Onix descends from a long
line of work in which the control plane is separated
from the dataplane [3-6, 15, 16,23], but Onix’s focus on
being a production-quality control platform for large-scale
networks led us to focus more on reliability, scalability,
and generality than previous systems. Ours is not the
first system to consider network control as a distributed
systems problem [10, 20], although we do not anticipate
the need to run our platform on end-hosts, due to
the flexibility of merchant silicon and other efforts to
generalize the forwarding plane [23], and the rapid
increase in power of commodity servers.
An orthogonal line of research focuses on offering
network developers an extensible forwarding plane (e.g.,
RouteBricks [11], Click [22] and XORP [17]); Onix is
complementary to these systems in offering an extensible
control plane. Similarly, Onix can be the platform
for flexible data center network architectures such as
SEATTLE [21], VL2 [14] and Portland [25] to manage
large data centers. This was explored somewhat in [30].
Other recent work [34] reduces the load of a centralized
controller by distributing network state amongst switches.
Onix focuses on the problem of providing generic dis-
tributed state management APIs at the controller, instead
of focusing on a particular approach to scale. We view
this work as distinct but compatible, as this technique
could be implemented within Onix.
Onix also follows the path of many earlier distributed
systems that rely on applications’ help to relax consis-
tency requirements in order to improve the efficiency of
state replication. Bayou [31], PRACTI [2], WheelFS [29]
and PNUTS [8] are examples of such systems.
9 Conclusion
The SDN paradigm uses the control platform to simplify
network control implementations. Rather than forcing
developers to deal directly with the details of the physical
infrastructure, the control platform handles the lower-
level issues and allows developers to program their
control logic on a high-level API. In so doing, Onix
essentially turns networking problems into distributed
systems problem, resolvable by concepts and paradigms
familiar for distributed systems developers.
However, this paper is not about the ideology of SDN,
but about its implementation. The crucial enabler of
this approach is the control platform, and in this paper
we present Onix as an existence proof that such control
platforms are feasible. In fact, Onix required no novel
mechanisms, but instead involves only the judicious use
of standard distributed system design practices.
What we should make clear, however, is that Onix
does not, by itself, solve all the problems of network
management. The designers of management applications
still have to understand the scalability implications of
their design. Onix provides general tools for managing
state, but it does not magically make problems of scale
and consistency disappear. We are still learning how to
build control logic on the Onix API, but in the examples
we have encountered so far management applications are
far easier to build with Onix than without it.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) 363
364
Acknowledgments
We thank the OSDI reviewers, and in particular our
shepherd Dave Andersen, for their helpful comments.
We also thank the various team members at Google, NEC,
and Nicira who provided their feedback on the design
and implementation of Onix. We gratefully acknowledge
Satoshi Hieda at NEC, who ran measurements that appear
in this paper.
References
[1]
[2]
[3]
[4]
[5]
[6]
[7]
[8]
[9]
[10]
[11]
[12]
[13]
[14]
[15]
802.lag - Connectivity Fault Management Standard. http: //
www.ieee802.org/1/pages/802.1lag.html.
BELARAMANI, N., DAHLIN, M., GAO, L., NAYATE, A.,
VENKATARAMANI, A., YALAGANDULA, P., AND ZHENG, J.
PRACTI Replication. In Proc. NSDI (May 2006).
CAESAR, M., CALDWELL, D., FEAMSTER, N., REXFORD,
J., SHAIKH, A., AND VAN DER MERWE, K._ Design and
Implementation of a Routing Control Platform. In Proc. NSDI
(April 2005).
CAI, Z., DINU, F., ZHENG, J., Cox, A. L., AND NG, T.
S. E. The Preliminary Design and Implementation of the
Maestro Network Control Platform. Tech. rep., Rice University,
Department of Computer Science, October 2008.
CASADO, M., FREEDMAN, M. J., PETTIT, J., Luo, J.,
McKEowN, N., AND SHENKER, S. Ethane: Taking Control
of the Enterprise. In Proc. SIGCOMM (August 2007).
CASADO, M., GARFINKEL, T., AKELLA, A., FREEDMAN, M. J.,
BONEH, D., MCKEOWN, N., AND SHENKER, S. SANE: A
Protection Architecture for Enterprise Networks. In Proc. Usenix
Security (August 2006).
CASADO, M., KOPONEN, T., RAMANATHAN, R., AND
SHENKER, S. Virtualizing the Network Forwarding Plane. In
Proc. PRESTO (November 2010).
COOPER, B. F., RAMAKRISHNAN, R., SRIVASTAVA, U.,
SILBERSTEIN, A., BOHANNON, P., JACOBSEN, H.-A., PUZ,
N., WEAVER, D., AND YERNENI, R. PNUTS: Yahoo!’s Hosted
Data Serving Platform. In Proc. VLDB (August 2008).
DECANDIA, G., HASTORUN, D., JAMPANI, M., KAKULAPATI,
G., LAKSHMAN, A., PILCHIN, A., SIVASUBRAMANIAN, S.,
VOSSHALL, P., AND VOGELS, W. Dynamo: Amazon’s Highly
Available Key-value Store. In Proc. SOSP (October 2007).
DIXON, C., KRISHNAMURTHY, A., AND ANDERSON, T. An
End to the Middle. In Proc. HotOS (May 2009).
DOBRESCU, M., EGI, N., ARGYRAKI, K., CHUN, B.-G.,
FALL, K., IANNACCONE, G., KNIES, A., MANESH, M., AND
RATNASAMY, S. RouteBricks: Exploiting Parallelism To Scale
Software Routers. In Proc. SOSP (October 2009).
FARREL, A., VASSEUR, J.-P., AND ASH, J. A Path Computation
Element (PCE)-Based Architecture, August 2006. RFC 4655.
GODFREY, P. B., GANICHEYV, I., SHENKER, S., AND STOICA, I.
Pathlet Routing. In Proc. SIGCOMM (August 2009).
GREENBERG, A., HAMILTON, J. R., JAIN, N., KANDULA,
S., Kim, C., LAHIRI, P., MALTZ, D. A., PATEL, P., AND
SENGUPTA, S. VL2: A Scalable and Flexible Data Center
Network. In Proc. SIGCOMM (August 2009).
GREENBERG, A., HJALMTYSSON, G., MALTZ, D. A., MYERS,
A., REXFORD, J., XIE, G., YAN, H., ZHAN, J., AND ZHANG, H.
A Clean Slate 4D Approach to Network Control and Management.
SIGCOMM CCR 35, 5 (2005), 41-54.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
16]
17]
21)
22]
[24]
[25]
[26]
[27]
[28]
[29]
[30]
[31]
[32]
[33]
[34]
GUDE, N., KOPONEN, T., PETTIT, J., PFAFF, B., CASADO, M.,
MCKEowWN, N., AND SHENKER, S. NOX: Towards an Operating
System for Networks. In SIGCOMM CCR (July 2008).
HANDLEY, M., KOHLER, E., GHOSH, A., HODSON, O., AND
RADOSLAVOV, P. Designing Extensible IP Router Software. In
Proc. NSDI (May 2005).
HINRICHS, T. L., GUDE, N. S., CASADO, M., MITCHELL, J. C.,
AND SHENKER, S. Practical Declarative Network Management.
In Proc. of SIGCOMM WREN (August 2009).
|] HUNT, P., KONAR, M., JUNQUEIRA, F. P., AND REED, B.
ZooKeeper: Wait-free Coordination for Internet-Scale Systems.
In Proc. Usenix Annual Technical Conference (June 2010).
JOHN, J. P., KATZ-BASSETT, E., KRISHNAMURTHY, A.,
ANDERSON, T., AND VENKATARAMANI, A. Consensus Routing:
The Internet as a Distributed System. In Proc. NSDI (April 2008).
KIM, C., CAESAR, M., AND REXFORD, J. Floodless in
SEATTLE: A Scalable Ethernet Architecture for Large Enterprises.
In Proc. SIGCOMM (August 2008).
KOHLER, E., MORRIS, R., CHEN, B., JANNOTTI, J., AND
KAASHOEK, M. F. The Click Modular Router. ACM Trans.
on Computer Systems 18, 3 (August 2000), 263-297.
McKEown, N., ANDERSON, T., BALAKRISHNAN, H.,
PARULKAR, G., PETERSON, L., REXFORD, J., SHENKER, S.,
AND TURNER, J. OpenFlow: Enabling Innovation in Campus
Networks. SIGCOMM CCR 38, 2 (2008), 69-74.
Multiprotocol Label Switching Working Group. http://
datatracker.ietf.org/wg/mpls/.
MYSORE, R. N., PAMBORIS, A., FARRINGTON, N., HUANG, N.,
MIRI, P., RADHAKRISHNAN, S., SUBRAM, V., AND VADHAT,
A. PortLand: A Scalable Fault-Tolerant Layer 2 Data Center
Network Fabric. In Proc. SIGCOMM (August 2009).
PFAFF, B., PETTIT, J., KOPONEN, T., AMIDON, K., CASADO,
M., AND SHENKER, S._ Extending Networking into the
Virtualization Layer. In Proc. HotNets (October 2009).
Private Network-Network Interface Specification Version 1.1
(PNNI 1.1), April 2002. ATM Forum.
SHERWOOD, R., GIBB, G., YAP, K.-K., APPENZELLER, G.,
CASADO, M., MCKEOWN, N., AND PARULKAR, G. Can the
Production Network Be the Testbed? In Proc. OSDI (October
2010).
STRIBLING, J., SOVRAN, Y., ZHANG, I., PRETZER, X., LI,
J., KAASHOEK, M. F., AND MorRIS, R. Flexible, Wide-Area
Storage for Distributed Systems with WheelFS. In Proc. NSDI
(April 2009).
TAVAKOLI, A., CASADO, M., KOPONEN, T., AND SHENKER,
S. Applying NOX to the Datacenter. In Proc. HotNets (October
2009).
TERRY, D. B., THEIMER, M. M., PETERSEN, K., DEMERS,
A. J., SPREITZER, M. J., AND HAUSER, C. H. Managing Update
Conflicts in Bayou, a Weakly Connected Replicated Storage
System. In Proc. SOSP (December 1995).
TOUCH, J., AND PERLMAN, R. Transparent Interconnection
of Lots of Links (TRILL): Problem and Applicability Statement.
RFC 5556, IETF, May 2009.
VMware vNetwork Distributed Switch, Simplify Virtual
Machine Networking. http://vmware.com/products/
vnetwork-distributed-switch.
Yu, M., REXFORD, J., FREEDMAN, M. J., AND WANG,
J. Scalable Flow-Based Networking with DIFANE. In Proc.
SIGCOMM (August 2010).
USENIX Association
USENIX Association
Can the Production Network Be the Testbed?
Rob Sherwood*, Glen Gibb’, Kok-Kiong Yap', Guido Appenzeller *,
Martin Casado°, Nick McKeown', Guru Parulkar*
* Deutsche Telekom Inc. R&D Lab, Los Altos, CA' Stanford University, Palo Alto, CA
° Nicira Networks, Palo Alto, CA
Abstract
A persistent problem in computer network research is
validation. When deciding how to evaluate a new feature
or bug fix, a researcher or operator must trade-off real-
ism (in terms of scale, actual user traffic, real equipment)
and cost (larger scale costs more money, real user traf-
fic likely requires downtime, and real equipment requires
vendor adoption which can take years). Building a realis-
tic testbed is hard because “real” networking takes place
on closed, commercial switches and routers with spe-
cial purpose hardware. But if we build our testbed from
software switches, they run several orders of magnitude
slower. Even if we build a realistic network testbed, it
is hard to scale, because it is special purpose and is in
addition to the regular network. It needs its own loca-
tion, support and dedicated links. For a testbed to have
global reach takes investment beyond the reach of most
researchers.
In this paper, we describe a way to build a testbed
that is embedded in—and thus grows with—the net-
work. The technique—embodied in our first prototype,
FlowVisor—slices the network hardware by placing a
layer between the control plane and the data plane. We
demonstrate that FlowVisor slices our own production
network, with legacy protocols running in their own
protected slice, alongside experiments created by re-
searchers. The basic idea is that if unmodified hardware
supports some basic primitives (in our prototype, Open-
Flow, but others are possible), then a worldwide testbed
can ride on the coat-tails of deployments, at no extra ex-
pense. Further, we evaluate the performance impact and
describe how FlowVisor is deployed at seven other cam-
puses as part of a wider evaluation platform.
1 Introduction
For many years the networking research community has
grappled with how best to evaluate new research ideas.
t Big Switch Networks, Palo Alto, CA
Today,
no clear path to
deployment
in Slice
Paper
Whiteboard NS2 VINI Wend
Plan OPNet Emulab FlowVisor A io ae
C/C++/Java Custom VMs Rte
(CONtROR Keres see sete stats ssese ese ee tte suse cast sens es siete eee Realist)
Figure 1: Today’s evaluation process is a continuum
from controlled but synthetic to uncontrolled but realistic
testing, with no clear path to vendor adoption.
Simulation [17, 19] and emulation [25] provide tightly
controlled environments to run repeatable experiments,
but lack scale and realism; they neither extend all the
way to the end-user nor carry real user traffic. Special
isolated testbeds [10, 22, 3] allow testing at scale, and
can carry real user traffic, but are usually dedicated to a
particular type of experiment and are beyond the budget
of most researchers.
Without the means to realistically test a new idea there
has been relatively little technology transfer from the re-
search lab to real-world networks. Network vendors are
understandably reluctant to incorporate new features be-
fore they have been thoroughly tested at scale, in realistic
conditions with real user traffic. This slows the pace of
innovation, and many good ideas never see the light of
day.
Peeking over the wall to the distributed systems com-
munity, things are much better. PlanetLab has proved in-
valuable as a way to test new distributed applications at
scale (over 1,000 nodes worldwide), realistically (it runs
real services, and real users opt in), and offers a straight-
forward path to real deployment (services developed in a
PlanetLab slice are easily ported to dedicated servers).
In the past few years, the networking research commu-
nity has sought an equivalent platform, funded by pro-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) 365
366
grams such as GENT [8], FIRE [6], etc. The goal is to
allow new network algorithms, features, protocols or ser-
vices to be deployed at scale, with real user traffic, on a
real topology, at line-rate, with real users; and in a man-
ner that the prototype service can easily be transferred
to run in a production network. Examples of experimen-
tal new services might include a new routing protocol,
a network load-balancer, novel methods for data center
routing, access control, novel hand-off schemes for mo-
bile users or mobile virtual machines, network energy
managers, and so on.
The network testbeds that come closest to achieving
this today are VINI [1] and Emulab [25]: both provide a
shared physical infrastructure allowing multiple simulta-
neous experiments to evaluate new services on a physi-
cal testbed. Users may develop code to modify both the
data plane and the control plane within their own isolated
topology. Experiments may run real routing software,
and expose their experiments to real network events. Em-
ulab is concentrated in one location, whereas VINI is
spread out across a wide area network.
VINI and Emulab trade off realism for flexibility in
three main ways.
Speed: In both testbeds packet processing and forwarding
is done in software by a conventional CPU. This makes
it easy to program a new service, but means it runs much
slower than in a real network. Real networks in enter-
prises, data centers, college campuses and backbones are
built from switches and routers based on ASICs. ASICs
consistently outperform CPU-based devices in terms of
data-rate, cost and power; for example, a single switch-
ing chip today can process over 600Gb/s [2].
Scale: Because VINI and Emulab don’t run new network-
ing protocols on real hardware, they must always exist as
a parallel testbed, which limits their scale. It would, for
example, be prohibitively expensive to build a VINI or
Emulab testbed to evaluate data-center-scale experiments
requiring thousands or tens of thousands of switches,
each with a capacity of hundreds of gigabits per second.
VINI’s geographic scope is limited by the locations will-
ing to host special servers (42 today). Without enormous
investment, it is unlikely to grow to global scale. Emu-
lab can grow larger, as it is housed under one roof, but
is still unlikely to grow to a size representative of a large
network.
Technology transfer: An experiment running on a net-
work of CPUs takes considerable effort to transfer to
specialized hardware; the development styles are quite
different, and the development cycle of hardware takes
many years and requires many millions of dollars.
But perhaps the biggest limitation of a dedicated
testbed is that it requires special infrastructure: equip-
ment has to be developed, deployed, maintained and sup-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
ported; and when the equipment is obsolete it needs to be
updated. Networking testbeds rarely last more than one
generation of technology, and so the immense engineer-
ing effort is quickly lost.
Our goal is to solve this problem. We set out to answer
the following question: can we build a testbed that is
embedded into every switch and router of the production
network (in college campuses, data centers, WANs, en-
terprises, WiFi networks, and so on), so that the testbed
would automatically scale with the global network, rid-
ing on its coat-tails with no additional hardware? If
this were possible, then our college campus networks—
for example—interconnected as they are by worldwide
backbones, could be used simultaneously for production
traffic and new WAN routing experiments; similarly, an
existing data center with thousands of switches can be
used to try out new routing schemes. Many of the goals
of programs like GENI and FIRE could be met without
needing dedicated network infrastructure.
In this paper, we introduce FlowVisor which aims to
turn the production network itself into a testbed (Fig-
ure 1). That is, FlowVisor allows experimenters to eval-
uate ideas directly in the production network (not run-
ning in a dedicated testbed alongside it) by “slicing” the
hardware already installed. Experimenters try out their
ideas in an isolated slice, without the need for dedicated
servers or specialized hardware.
1.1. Contributions.
We believe our work makes five main contributions:
Runs on deployed hardware and at real line-rates.
FlowVisor introduces a software slicing layer between
the forwarding and control planes on network devices.
While FlowVisor could slice any control plane message
format, in practice we implement the slicing layer with
OpenFlow [16]. To our knowledge, no previously pro-
posed slicing mechanism allows a user-defined control
plane to control the forwarding in deployed production
hardware. Note that this would not be possible with
VLANs—while they crudely separate classes of traffic,
they provide no means to control the forwarding plane.
We describe the slicing layer in §2 and FlowVisor’s
architecture in §3.
Allows real users to opt-in on a per-flow basis.
FlowVisor has a policy language that maps flows
to slices. By modifying this mapping, users can easily
try new services, and experimenters can entice users to
bring real traffic. We describe the rules for mapping
flows to slices in §3.2.
Ports easily to non-sliced networks. FlowVisor (and its
slicing) is transparent to both data and control planes,
and therefore, the control logic is unaware of the slicing
USENIX Association
USENIX Association
layer. This property provides a direct path for vendor
adoption. In our OpenFlow-based implementation, nei-
ther the OpenFlow switches or the controllers need be
modified to interoperate with FlowVisor (§3.3).
Enforces strong isolation between slices. Flow Visor
blocks and rewrites control messages as they cross the
slicing layer. Actions of one slice are prevented from
affecting another, allowing experiments to safely coexist
with real production traffic. We describe the details
of the isolation mechanisms in 84 and evaluate their
effectiveness in §5.
Operates on deployed networks FlowVisor has been
deployed in our production campus network for the last 7
months. Our deployment consists of 20+ users, 40+ net-
work devices, a production traffic slice, and four stand-
ing experimental slices. In §6, we describe our cur-
rent deployment and future plans to expand into seven
other campus networks and two research backbones in
the coming year.
2 Slicing Control & Data Planes
On today’s commercial switches and routers, the con-
trol plane and data planes are usually logically distinct
but physically co-located. The control plane creates and
populates the data plane with forwarding rules, which
the data plane enforces. In a nutshell, FlowVisor as-
sumes that the control plane can be separated from the
data plane, and it then slices the communication between
them. This slicing approach can work several ways: for
example, there might already be a clean interface be-
tween the control and data planes inside the switch. More
likely, they are separated by a common protocol (e.g.,
OpenFlow [16] or ForCes [7]). In either case, Flow Visor
sits between the control and data planes, and from this
vantage point enables a single data plane to be controlled
by multiple control planes—each belonging to a separate
experiment.
With FlowVisor, each experiment runs in their own
slice of the network. A researcher, Bob, begins by re-
questing a network slice from Alice, his network admin-
istrator. The request specifies his requirements including
topology, bandwidth, and the set of traffic—defined by a
set of flows, or flowspace—that the slice controls. Within
his slice, Bob has his own control plane where he puts the
control logic that defines how packets are forwarded and
rewritten in his experiment. For example, imagine that
Bob wants to create a new /ttp load-balancer to spread
port 80 traffic over multiple web servers. He requests
a Slice: its topology should encompass the web servers,
and its flowspace should include all flows with port 80.
He is allocated a control plane where he adds his load-
balancing logic to control how flows are routed in the
: Proprietary :
: Control Logic :
t
Protocol Controllers
: Forwardi g
Logic
OpenFlow
Generic Sliced
Switch Architecture
Classical
Switch Architecture
Sliced OpenFlow
Switch Architecture
Figure 2: Classical network device architectures have
distinct forwarding and control logic elements (left). By
adding a transparent slicing layer between the forward-
ing and control elements, FlowVisor allows multiple
control logics to manage the same forwarding element
(middle). In implementation, FlowVisor uses OpenFlow
and sits between an OpenFlow switch—the forwarding
element—and multiple OpenFlow controllers—the con-
trol logic (right).
data plane. He may advertise his new service so as to at-
tract users. Interested users “opt-in” by contacting their
network administrator to add a subset of their flows to
the flowspace of Bob’s slice.
In this example, FlowVisor allocates a control plane
for Bob, and allows him to control his flows (but no oth-
ers) in the data plane. Any events associated with his
flows (e.g. when a new flow starts) are sent to his control
plane. FlowVisor enforces his slice’s topology by only
allowing him to control switches within his slice.
FlowVisor slices the network along multiple dimen-
sions, including topology, bandwidth, and forwarding
table entries. Slices are isolated from each other, so
that actions in one slice—be they faulty, malicious, or
otherwise—do not impact other slices.
2.1 Slicing OpenFlow
While architecturally FlowVisor can slice any data
plane/control plane communication channel, we built our
prototype on top of OpenFlow.
OpenFlow [16, 18] is an open standard that allows re-
searchers to directly control the way packets are routed
in the network. As described above, in a classical net-
work architecture, the control logic and the data path are
co-located on the same device and communicate via an
internal proprietary protocol and bus. In OpenFlow, the
control logic is moved to an external controller (typi-
cally a commodity PC); the controller talks to the dat-
apath (over the network itself) using the OpenFlow pro-
tocol (Figure 2, right). The OpenFlow protocol abstracts
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) 367
368
- Alice's 4
Detour
Node
Figure 3: FlowVisor allows users (Doug) to delegate
control of subsets of their traffic to distinct researchers
(Alice, Bob, Cathy). Each research experiment runs in
its own, isolated network slice.
forwarding/routing directives as “flow entries”. A flow
entry consists of a bit pattern, a list of actions, and a set
of counters. Each flow entry states “perform this list of
actions on all packets in this flow” where a typical action
is “forward the packet out port X” and the flow is defined
as the set of packets that match the given bit pattern. The
collection of flow entries on a network device is called
the “flow table”.
When a packet arrives at a switch or router, the device
looks up the packet in the flow table and performs the
corresponding set of actions. If the packet doesn’t match
any entry, the packet is queued and a new flow event is
sent across the network to the OpenFlow controller. The
controller responds by adding a new rule to the flow table
to handle the queued packet. Subsequent packets in the
same flow will be handled without contacting the con-
troller. Thus, the external controller need only be con-
tacted for the first packet in a flow; subsequent packets
are forwarded at the switch’s full line rate.
Architecturally, OpenFlow exploits the fact that mod-
ern switches and routers already logically implement
flow entries and flow tables—typically in hardware as
TCAMs. As such, a network device can be made
OpenFlow-compliant via firmware upgrade.
Note that while OpenFlow allows researchers to
experiment with new network protocols on deployed
hardware, only a single researcher can use/control an
OpenFlow-enabled network at a time. As a result, with-
out FlowVisor, OpenFlow-based research is limited to
isolated testbeds, limiting its scope and realism. Thus,
FlowVisor’s ability to slice a production network is an or-
thogonal and indepenent contribution to OpenFlow-like
software-defined networks.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
3 FlowVisor Design
To restate our main goal, FlowVisor aims to use the pro-
duction network as a testbed. In operation, the Flow Visor
slices the network by slicing each of the network’s corre-
sponding packet forwarding devices (e.g., switches and
routers) and links (Figure 3).
With the FlowVisor,
e Network resources are sliced in terms of their band-
width, topology, forward table entries, and device CPU
(§3.1).
e Each slice has control over a set of flows, called its
flowspace. Users can arbitrarily add (opt-in) and remove
(opt-out) their own flows from a slice’s flowspace at any-
time (§3.2).
e Each slice has its own distinct, programmable con-
trol logic, that manages how packets are forwarded and
rewritten for traffic in the slice’s flowspace. In practice,
each slice owner implements their slice-specific control
logic as an OpenFlow controller. The FlowVisor inter-
poses between data and control planes by proxying con-
nections between OpenFlow switches and each slice con-
troller (§3.3).
e Slices are defined using a slice definition policy lan-
guage. The language specifies the slice’s resource limits,
flowspace, and controller’s location in terms of IP and
TCP port-pair (83.4).
3.1 Slicing Network Resources
Slicing a network means correctly slicing all of the cor-
responding network resources. There are four primary
slicing dimensions:
Topology. Each slice has its own view of network nodes
(e.g., switches and routers) and the connectivity between
them. In this way, slices can experience simulated net-
work events such as link failure and forwarding loops.
Bandwidth. Each slice has its own fraction of bandwidth
on each link. Failure to isolate bandwidth would allow
one slice to affect, or even starve, another slice’s through-
put.
Device CPU. Each slice is limited to what fraction of
each device’s CPU that it can consume. Switches
and routers typically have very limited general purpose
computational resources. Without proper CPU slicing,
switches will stop forwarding slow-path packets (§5.3.2),
drop statistics requests, and, most importantly, will stop
processing updates to the forwarding table.
Forwarding Tables. Each slice has a finite quota of for-
warding rules. Network devices typically support a finite
USENIX Association
USENIX Association
Alice's Bob's Cathy's
Controller Controller Controller
Resource :
PODEHETS Allocation +
Policy
Slice Def.
0... 0
s
Figure 4: The FlowVisor intercepts OpenFlow messages
from guest controllers (1) and, using the user’s slicing
policy (2), transparently rewrites (3) the message to con-
trol only a slice of the network. Messages from switches
(4) are forwarded only to guests if it matches their slice
policy.
number of forwarding rules (e.g., TCAM entries). Fail-
ure to isolate forwarding entries between slices might al-
low one slice to prevent another from forwarding pack-
ets.
3.2 Flowspace and Opt-In
A slice controls a subset of traffic in the network. The
subset is defined by a collection of packet headers that
form a well-defined (but not necessarily contiguous) sub-
space of the entire space of possible packet headers. Ab-
stractly, if packet headers have n bits, then the set of
all possible packet header forms an n-dimensional space.
An arriving packet is a single point in that space repre-
senting all packets with the same header. Similar to the
geometric representation used to describe access control
lists for packet classification [14], we use this abstrac-
tion to partition the space into regions (flowspace) and
map those regions to slices.
The flowspace abstraction helps us manage users who
opt-in. To opt-in to a new experiment or service, users
signal to the network administrator that they would like
to add a subset of their flows to a slice’s flowspace. Users
can precisely decide their level of involvement in an ex-
periment. For example, one user might opt-in all of their
traffic to a single experiment, while another user might
just opt-in traffic for one application (e.g., port 80 for
HTTP), or even just a specific flow (by exactly specify-
ing all of the fields of a header). In our prototype the
opt-in process is manual; but in a ideal system, the user
would be authenticated and their request checked auto-
matically against a policy.
For the purposes of testbed we concluded flow-level
opt-in is adequate—in fact, it seems quite powerful. An-
other approach might be to opt-in individual packets,
which would be more onerous.
3.3. Control Message Slicing
By design, FlowVisor is a slicing layer interposed be-
tween data and control planes of each device in the net-
work. In implementation, FlowVisor acts as a transpar-
ent proxy between OpenFlow-enabled network devices
(acting as dumb data planes) and multiple OpenFlow
slice controllers (acting as programmable control logic—
Figure 4). All OpenFlow messages between the switch
and the controller are sent through FlowVisor. FlowVi-
sor uses the OpenFlow protocol to communicate upwards
to the slice controllers and and downwards to OpenFlow
switches. Because FlowVisor is transparent, the slice
controllers require no modification and believe they are
communicating directly with the switches.
We illustrate the FlowVisor’s operation by extend-
ing the example from §2 (Figure 4). Recall that a re-
searcher, Bob, has created a slice that is an HTTP proxy
designed to spread all HTTP traffic over a set of web
servers. While the controller will work on any HTTP
traffic, Bob’s FlowVisor policy slices the network so
that he only sees traffic from users that have opted-in
to his slice. His slice controller doesn’t know the net-
work has been sliced, so doesn’t realize it only sees a
subset of the HTTP traffic. The slice controller thinks
it can control, i.e., insert flow entries for, all HTTP traf-
fic from any user. When Bob’s controller sends a flow
entry to the switches (e.g., to redirect HTTP traffic to
a particular server), FlowVisor intercepts it (Figure 4-
1), examines Bob’s slice policy (Figure 4-2), and re-
writes the entry to include only traffic from the allowed
source (Figure 4-3). Hence the controller is controlling
only the flows it is allowed to, without knowing that the
FlowVisor is slicing the network underneath. Similarly,
messages that are sourced from the switch (e.g., a new
flow event—Figure 4-4) are only forwarded to guest con-
trollers whose flowspace match the message. That is, it
will only be forwarded to Bob if the new flow is HTTP
traffic from a user that has opted-in to his slice.
Thus, FlowVisor enforces transparency and isolation
between slices by inspecting, rewriting, and policing
OpenFlow messages as they pass. Depending on the re-
source allocation policy, message type, destination, and
content, the FlowVisor will forward a given message un-
changed, translate it to a suitable message and forward,
or “bounce” the message back to its sender in the form
of an OpenFlow error message. For a message sent
from slice controller to switch, FlowVisor ensures that
the message acts only on traffic within the resources as-
signed to the slice. For a message in the opposite di-
rection (switch to controller), the FlowVisor examines
the message content to infer the corresponding slice(s)
to which the message should be forwarded. Slice con-
trollers only receive messages that are relevant to their
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) 369
370
Bob's Cathy's ‘i Goan ‘
: _—:
Controller | | Controller + Connection :
5 5
Alice's . Eric's
FlowVisor Controller
4.
FlowVisor FlowVisor
Figure 5: FlowVisor can trivially recursively slice an al-
ready sliced network, creating hierarchies of Flow Visors.
network slice. Thus, from a slice controller’s perspec-
tive, FlowVisor appears as a switch (or a network of
switches); from a switch’s perspective, FlowVisor ap-
pears as a controller.
FlowVisor does not require a 1-to-1 mapping between
FlowVisor instances and physical switches. One FlowVi-
sor instance can slice multiple physical switches, and
even re-slice an already sliced network (Figure 5) .
3.4 Slice Definition Policy
The slice policy defines the network resources, flows-
pace, and OpenFlow slice controller allocated to each
slice. Each policy is described by a text configuration
file—one file per slice. In terms of resources, the policy
defines the fraction of total link bandwidth available to
this slice ($4.3) and the budget for switch CPU and for-
warding table entries. Network topology is specified as a
list of network nodes and ports.
The flowspace for each slice is defined by an ordered
list of tuples similar to firewall rules. Each rule descrip-
tion has an associated action, e.g., allow, read-only, or
deny, and is parsed in the specified order, acting on the
first matching rule. The rules define the flowspace a slice
controls. Read-only rules allow slices to receive Open-
Flow control messages and query switch statistics, but
not to write entries into the forwarding table. Rules are
allowed to overlap, as described in the example below.
Let’s take a look at an example set of rules. Alice, the
network administrator, wants to allow Bob to conduct an
HTTP load-balancing experiment. Bob has convinced
some of his colleagues to opt-in to his experiment. Al-
ice wants to maintain control of all traffic that is not part
of Bob’s experiment. She wants to passively monitor all
network performance, to keep an eye on Bob and the pro-
duction network.
Here is a set of rules Alice could install in the FlowVi-
sor:
Bob’s Experimental Network includes all HTTP traffic
to/from users who opted into his experiment. Thus, his
network is described by one rule per user:
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
Allow: tcp_port:80 and ip=user_ip.
OpenFlow messages from the switch matching any of
these rules are forwarded to Bob’s controller. Any flow
entries that Bob tries to insert are modified to meet these
rules.
Alice’s Production Network is the complement of Bob’s
network. For each user in Bob’s experiment, the produc-
tion traffic network has a negative rule of the form:
Deny: tcp-port:80 and ip=user_ip. The
production network would have a final rule that matches
all flows: Allow: all.
Thus, only OpenFlow messages that do not go to Bob’s
network are sent to the production network controller.
The production controller is allowed to insert forwarding
entries so long as they do not match Bob’s traffic.
Alice’s Monitoring Network is allowed to see all traffic
in all slices. Ithas one rule, Read-only: all.
This rule-based policy, though simple, suffices for the
experiments and deployment described in this paper. We
expect that future Flow Visor deployments will have more
specialized policy needs, and that researchers will create
new resource allocation policies.
4 FlowVisor Implementation
We implemented FlowVisor in approximately 8000 lines
of C and the code is publicly available for download
from www. openflow.org. The notable parts of the im-
plementation are the transparency and isolation mech-
anisms. Critical to its design, FlowVisor acts as a
transparent slicing layer and enforces isolation between
slices. In this section, we describe how FlowVisor
rewrites control messages—both down to the forwarding
plane and up to the control plane—to ensure both trans-
parency and strong isolation. Because isolation mech-
anisms vary by resource, we describe each resource in
turn: bandwidth, switch CPU, and forwarding table en-
tries. In our deployment, we found that the switch CPU
was the most constrained resource, so we devote partic-
ular care to describing its slicing mechanisms.
4.1 Messages to Control Plane
FlowVisor carefully rewrites messages from the Open-
Flow switch to the slice controller to ensure transparency.
First, FlowVisor only sends control plane messages to
a slice controller if the source switch is actually in the
slice’s topology. Second, FlowVisor rewrites Open-
Flow feature negotiation messages so that the slice con-
troller only sees the physical switch ports that appear
in the slice. Third, OpenFlow port up/port down mes-
sages are similarly pruned and only forwarded to the af-
fected slices. Using these message rewriting techniques,
USENIX Association
USENIX Association
FlowVisor can easily simulate network events, such as
link and node failures.
4.2 Messages to Forwarding Plane
In the opposite direction, FlowVisor also rewrites mes-
sages from the slice controller to the OpenFlow switch.
The most important messages to the forwarding plane
were insertions and deletions to the forwarding table.
Recall (§2.1) that in OpenFlow, forwarding rules consist
of a flow rule definition, i.e., a bit pattern, and a set of
actions. To ensure both transparency and isolation, the
Flow Visor rewrites both the flow definition and the set of
actions so that they do not violate the slice’s definition.
Given a forwarding rule modification, the FlowVisor
rewrites the flow definition to intersect with the slice’s
flowspace. For example, Bob’s flowspace gives him con-
trol over HTTP traffic for the set of users—e.g., users
Doug and Eric—that have opted into his experiment. If
Bob’s slice controller tried to create a rule that affected
all of Doug’s traffic (HTTP and non-HTTP), then the
FlowVisor would rewrite the rule to only affect the in-
tersection, i.e., only Doug’s HTTP traffic. If the inter-
section between the desired rule and the slice definition
is null, e.g., Bob tried to affect traffic outside of his
slice, e.g.., Doug’s non-HTTP traffic, then the FlowVi-
sor would drop the control message and return an error
to Bob’s controller. Because flowspaces are not necessar-
ily contiguous, the intersection between the desired rule
and the slice’s flowspace may result in a single rule be-
ing expanded into multiple rules. For example, if Bob
tried to affect all traffic in the system in a single rule, the
FlowVisor would transparently expand the single rule in
to two rules: one for each of Doug’s and Eric’s HTTP
traffic.
FlowVisor also rewrites the lists of actions in a for-
warding rule. For example, if Bob creates a rule to send
out all ports, the rule is rewritten to send to just the sub-
set of ports in Bob’s slice. If Bob tries to send out a port
that is not in his slice, the FlowVisor returns a “action
is invalid” error (recall that from above, Bob’s controller
only discovers the ports that do exist in his slice, so only
in error would he use a port outside his slice).
4.3 Bandwidth Isolation
Typically, even relatively modest commodity network
hardware has some capability for basic bandwidth iso-
lation [13]. The most recent versions of OpenFlow ex-
pose native bandwidth slicing capabilities in the form of
per-port queues. The FlowVisor creates a per-slice queue
on each port on the switch. The queue is configured for
a fraction of link bandwidth, as defined in the slice def-
inition. To enforce bandwidth isolation, the FlowVisor
rewrites all slice forwarding table additions from “send
out port X” to “send out queue Y on port X ”, where Y
is a slice-specific queue ID. Thus, all traffic from a given
slice is mapped to the traffic class specified by the re-
source allocation policy. While any queuing discipline
can be used (weighted fair queuing, deficit round robin,
strict partition, etc.), in implementation, FlowVisor uses
minimum bandwidth queues. That is, a slice configured
for X % of bandwidth will receive at least X% and pos-
sibly more if the link is under-utilized. We choose min-
imum bandwidth queues to avoid issues of bandwidth
fragmentation. We evaluate the effectiveness of band-
width isolation in §5.
4.4 Device CPU Isolation
CPUs on commodity network hardware are typically
low-power embedded processors and are easily over-
loaded. The problem is that in most hardware, a highly-
loaded switch CPU will significantly disrupt the network.
For example, when a CPU becomes overloaded, hard-
ware forwarding will continue, but the switch will stop
responding to OpenFlow requests, which causes the for-
warding tables to enter an inconsistent state where rout-
ing loops become possible, and the network can quickly
become unusable.
Many of the CPU-isolation mechanisms presented are
not inherent to FlowVisor’s design, but rather a work-
around to deal with the existing hardware abstraction ex-
posed by OpenFlow. A better long-term solution would
be to expose the switch’s existing process scheduling
and rate-limiting features via the hardware abstraction.
Some architectures, e.g., the HP ProCurve 5400, already
use rate-limiters to enforce CPU isolation between Open-
Flow and non-OpenFlow VLANs. Adding these features
to OpenFlow is ongoing.
There are four main sources of load on a switch CPU:
(1) generating new flow messages, (2) handling requests
from controller, (3) forwarding “slow path” packets, and
(4) internal state keeping. Each of these sources of load
requires a different isolation mechanism.
New Flow Messages. In OpenFlow, when a packet
arrives at a switch that does not match an entry in the
flow table, a new flow message is sent to the controller.
This process consumes processing resources on a switch
and if message generation occurs too frequently, the CPU
resources can be exhausted. To prevent starvation, the
FlowVisor rate limits the new flow message arrival rate.
In implementation, the FlowVisor tracks the new flow
message arrival rate for each slice, and if it exceeds some
threshold, the Flow Visor inserts a forwarding rule to drop
the offending packets for a short period.
For example, the FlowVisor keeps a token-bucket style
counter for each flow space rule (“Bob’s slice gets (1)
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI’10) 371
372
all HTTP traffic and (2) all HTTPS traffic’, ie., two
rules/counters). Each time the FlowVisor receives a
new flow event, the token bucket that matches the flow
gets decremented (for Bob’s slice, packets that match
HTTP count against token bucket #1, packets that match
HTTPS count against #2). Once the bucket is emp-
tied, the FlowVisor inserts a lowest-priority rule into the
switch to drop all packets in that flowspace rule, ie.,
from the example, if the token bucket corresponding to
HTTPS is emptied, then the flowvisor will cause the
switch to drop all HTTPS packets—without generating
new flow events. The rule is set to expire in | second, so
it is effectively a very coarse rate limiter. In practice, if
a slice has control over “all traffic”, this mechanism ef-
fectively blocks all new flow events from saturating the
switch CPU or going to the controller, while allowing all
existing flows to continue without change. We discuss
the effectiveness of this technique in 85.3.2.
Controller Requests. The requests an OpenFlow con-
troller sends to the switch, e.g., to edit the forwarding
table or query statistics, consume CPU resources. For
each slice, the FlowVisor limits CPU consumption by
throttling the OpenFlow message rate to a maximum rate
per second. Because the amount of CPU resources con-
sumed vary by message type and by hardware implemen-
tation, it is future work to dynamically infer the cost of
each OpenFlow message for each hardware platform.
Slow-Path Forwarding. Packets that traverse the
“slow” path—i.e., not the “fast” dedicated hardware for-
warding path—consume CPU resources. Thus, an Open-
Flow rule that forwards packets via the slow path can
consume arbitrary CPU resources.
This is because, in implementations, most switches
only implement a subset of OpenFlow’s functionality
in their hardware. For example, the ASICs on most
switches do not support sending one packet out exactly
two ports (they support unicast and broadcast, but not
in between). To emulate this behavior, the switches ac-
tually process these types of flows in their local CPUs,
i.e,. on their slow path. Unfortunately, as mentioned
above, these are embedded CPUs and are not as powerful
as those on, for example, commodity PCs.
FlowVisor prevents slice controllers from insert-
ing slow-path forwarding rules by rewriting them as
one-time packet forwarding events, i.e., an OpenFlow
“packet out” message. As a result, the slow-path packets
are rate limited by the above two isolation mechanisms:
new flow messages and controller request rate limiting.
Internal Bookkeeping. All network devices use CPU
to update their internal counters, process events, update
counters, etc. So, care must be taken to ensure that there
is sufficient CPU available for the switch’s bookkeep-
ing. The FlowVisor accounts for this by ensuring that
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
the above rate limits are tuned to leave sufficient CPU
resources for the switch’s internal function.
4.5 Flow Entry Isolation
The FlowVisor counts the number of flow entries used
per slice and ensures that each slice does not exceed a
preset limit. The FlowVisor increments a counter for
each rule a guest controller inserts into the switch and
then decrements the counter when a rule expires. Due
to hardware limitations, certain switches will internally
expand rules that match multiple input ports, so the
FlowVisor needs to handle this case specially. When a
guest controller exceeds its flow entry limit, any new rule
insertions received a “table full” error message.
5 Evaluation
To motivate the efficiency and robustness of the design,
in this section we evaluate the FlowVisor’s scalability,
performance, and isolation properties.
5.1 Scalability
A single FlowVisor instance scales well enough to serve
our entire 40+ switch, 7 slice deployment with minimal
load. As a result, we create an artificially high work-
load to evaluate our implementation’s scaling limits. The
FlowVisor’s workload is characterized by the number of
switches, slices, and flowspace rules per slice as well as
the rate of new flow messages. We present the results for
two types of workloads: one that matches what we ob-
serve from our deployment (1 slice, 35 rules per slice, 28
switches!, 1.55 new flows per second per switch) and the
other a synthetic workload (10 switches, 100 new flows
per second per switch, | slice, 1000 rules per slice) de-
signed to stress the system. In each graph, we fix three
variables according to their workload and vary the forth.
Our evaluation measured FlowVisor’s CPU utilization
using a custom script. The script creates a configurable
number of OpenFlow connections to the FlowVisor, and
each connections simulates a switch that sends new flow
messages to the FlowVisor at a prescribed rate. With
each experiment, we configured the FlowVisor’s num-
ber of slices and flowspace rules per slice. The new
flow messages were carefully crafted to match only the
last rule of each slice, causing the worst case behavior
in the FlowVisor’s linear search of the flowspace rules.
Each test was run for 5 minutes and we recorded the
CPU utilization of the FlowVisor process once per sec-
ond, so each result is the average of 300 samples (shown
!This particular measurement did not include all of the switches in
out network.
USENIX Association
USENIX Association
Synthe'
Synthetic:
1000 rules/slice
% CPU utilization
% CPU utilization
Observed:
37 rules/slice
0 200 400 600 800 1000 1200 1400 1000
New flows per switch per sec (1 switch, 1 slice)
500
tic:
100 new flows/switch/s
10 switches
} Observed:
1.5 new flgws/swit¢h/s; 28 witches
1500
Number of rules per slice (1 slice)
Synthetic:
1000 rules/slice 10 switches
100 new flows/switch/s
Observed:
37 rules/slice 28 switches }
7 1.55 tnew flows/switch/s ;
% CPU utilization
2000 2500 3000
Number of slices
Figure 6: FlowVisor scales linearly with new flow rate, number of slices, switches, and flowspace rules. We generate
high synthetic workloads to explore the scalability because the workloads observed in our deployment were non-
taxing.
with one standard deviation). The FlowVisor ran on a
quad-core Intel Xeon 3GHz system running 32-bit De-
bian Linux 5.0 (Lenny).
Our results with the synthetically high workload show
that the FlowVisor’s CPU load scales linearly in each of
these four workload dimensions (as summarized in Fig-
ure 6). The result is promising, but not surprising. In-
tuitively, the FlowVisor can process a fixed number of
OpenFlow messages per second (the product of number
of switches by new flow rate) and each message must
be matched against each rule of each slice, so the to-
tal load is approximately the product of the four work-
load variables. The synthetic workload with 1,000 new
flows/s (10 switches by 100 new flows/s) is comparable
to the peak rate of published real-world enterprise net-
works [20]: an 8,000 host network generated a peak rate
of 1,200 new flows per second. Thus, we believe that
a single FlowVisor instance could manage a large enter-
prise network. By contrast, our observed workload fluc-
tuated between 0% and 10% CPU, roughly independent
of the experimental variable. This validates our belief
that our deployment can grow significantly using a sin-
gle FlowVisor instance.
While our results show that FlowVisor scales well be-
yond our current requirements and workload, it is worth
noting that it is possible to achieve even further scaling
by moving to a multi-threaded implementation (the cur-
rent implementation is single threaded) or even to multi-
ple FlowVisor instances.
5.2 Performance Overhead
Adding an additional layer between control and data
planes adds overhead to the system. However, as a re-
sult of our design, the FlowVisor does not add over-
head to the data plane. That is, with FlowVisor, packets
are forwarded at full line rate. Nor does the FlowVisor
add overhead to the control plane: control-level calcula-
tions like route selection proceed at their un-sliced rate.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
FlowVisor only adds overhead to actions that cross be-
tween the control and data plane layers.
To quantify this cross-layer overhead, we measure the
increased response time for slice controller requests with
and without the FlowVisor. Specifically, we consider
the response time of the OpenFlow messages most com-
monly used in our network and by our monitoring soft-
ware: the new flow and the port status request messages.
In OpenFlow, a switch sends a new flow message to
its controller when an arriving packet does not match any
existing forwarding rules. We examine the increased de-
lay of the new flow message to better understand how
the FlowVisor affects connection setup latency. In our
experiment, we connect a machine with two interfaces to
a switch. One interface sends a packet every 20ms (50
packets per second) to the switch and the other interface
is the OpenFlow control channel. We measure the time
between sending the packet and receiving the new flow
message using libpcap. Our results (Figure 7(a)) show
that the Flow Visor increases time from the switch to con-
troller by an average of 16ms. For latency sensitive ap-
plications, e.g., web services in large data centers, 16ms
may be too much overhead. However, new flow mes-
sages add 12ms latency on average even without FlowVi-
sor, so we believe that slice controllers in those envi-
ronments will likely proactively insert flow entries into
switches, avoiding this latency all together. We point out
that the algorithm FlowVisor uses to process new flow
messages is naive, and its run-time grows linearly with
the number of flowspace rules (85.1). We are yet to ex-
periment with the many classification algorithms that can
be expected to improve the lookup speed.
A port status request is a message sent by the con-
troller to the switch to query the byte and packet coun-
ters for a specific port. The switch returns the counters
in a corresponding port status reply message. We choose
to study the port status request because we believe it to
be a worst case for FlowVisor overhead. The message
is very cheap to process at the switch and controller, but
expensive for the FlowVisor to process: it has to edit the
313
374
without FlowVisor
with FlowVisor
Avg overhead:
16.16 ms
Cumulative Probability
0.1 1 10
OpenFlow New Flow Latency (ms)
100
j Avg overhead:
0.483ms
Cumulative Probability
/without FlowVisor
Zé. With FlowVisor
10 100
OpenFlow Port Status Latency (ms)
Figure 7: CDF of slicing overhead for OpenFlow new flow messages and port status requests.
message per slice to remove statistics for ports that do
not appear in a sliced topology.
We wrote a special-purpose controller that sent ap-
proximately 200 port status requests per second and mea-
sured the response times. The rate was chosen to ap-
proximate the maximum request rate supported by the
hardware. The controller, switch, and FlowVisor were
all on the same local area network, but controller and
FlowVisor were hosted on separate PCs. Obviously, the
overhead can be increased by moving the FlowVisor ar-
bitrarily far away from the controller, but we design this
experiment to quantify the FlowVisor’s processing over-
head. Our results show that adding the FlowVisor causes
an average overhead for port status responses of 0.48 mil-
liseconds(Figure 7(b)). We believe that port status re-
sponse time being faster than new flow processing time
is not inherent, but simply a matter of better optimization
for port status request handling.
5.3 Isolation
5.3.1 Bandwidth
To validate the FlowVisor’s bandwidth isolation prop-
erties, we run an experiment where two slices compete
for bandwidth on a shared link. We consider the worst
case for bandwidth isolation: the first slice sends TCP-
friendly traffic and the other slice sends TCP-unfriendly
constant-bit-rate (CBR) traffic at full link speed (1Gbps).
We believe these traffic patterns are representative of a
scenario where production slice (TCP) shares a link with,
for example, a slice running a DDoS experiment (CBR).
This experiment uses 3 machines—two sources and a
common sink—all connected via the same HP ProCurve
5400 switch, i.e., the switch found in our wiring closet.
The traffic is generated by iperf in TCP mode for the
TCP traffic and UDP mode at 1Gbps for the CBR traffic.
We repeat the experiment twice: with and without the
FlowVisor’s bandwidth isolation features enabled (Fig-
ure 8(a)). With the bandwidth isolation disabled (“with-
out Slicing”), the CBR traffic consumes nearly all the
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
bandwidth and the TCP traffic averages 1.2% of the link
bandwidth. With the traffic isolation features enabled
(“with 30/70% reservation’), the FlowVisor maps the
TCP slice to a QoS class that guarantees at least 70%
of link bandwidth and maps the CBR slice to a class that
guarantees at least 30%. Note that theses are minimum
bandwidth guarantees, not maximum. With the band-
width isolation features enabled, the TCP slice achieves
an average of 64.2% of the total bandwidth and the CBR
an average of 28.5%. Note that the event at 20 seconds
where the CBR with QoS jumps and the TCP with QoS
experiences a corresponding dip. We believe this to be
the result of a TCP congestion event that allowed the
CBR traffic to temporarily take advantage of additional
available bandwidth, exactly as the minimum bandwidth
queue is designed.
5.3.2 Switch CPU
To quantify our ability to isolate the switch CPU re-
source, we show two experiments that monitor CPU-
usage over time of a switch with and without isolation
enabled. In the first experiment (Figure 8(b)), the Open-
Flow controller maliciously sends port stats request mes-
sages (as above) at increasing speeds (2,4,8...1024
requests per second). In our second experiment (Fig-
ure 8(c)), the switch generates new flow messages faster
than its CPU can handle and a faulty controller does not
add a new rule to match them. In both experiments, we
show the switch’s CPU utilization averaged over one sec-
ond, and the FlowVisor’s isolation features reduce the
switch utilization from 100% to a configurable amount.
In the first experiment, we note that the switch could han-
dle less than 256 port status requests without appreciable
CPU load, but immediately goes to 100% load when the
request rate hits 256 requests per second. In the second
experiment, the bursts of CPU activity in Figure 8(c) is
a direct result of using null forwarding rules (84.4) to
rate limit incoming new flow messages. We expect that
future versions of OpenFlow will better expose the hard-
ware CPU limiting features already in switches today.
USENIX Association
USENIX Association
CBR without Slicing | osu
Without Isolation
With Isolation
< ——hy 2 S
Ss & o wo wo 2 es eB 0:
3 TCP with 70% © 2 4 68 BS 18% 8B 3
2 joo Reservation nf OS Bie oe Be 8 ee Se =
e i. ee 5 © OO S'S BB TIT o 5
o re SIS FT TFT GFid9 oO:
a 2 gS 22 8 cigs S| 2
= CBR with 30% 5 SF SF oa +t A iH + S 5
oO R ti Y OF MOM GO FIN wi =
o & t &
5 2 i 2
a : a = =
TCP without Slicing a 2) Without Isolation
With Isolation
0 5 10 15 20 25 30 35 40 0 5 10 15 20 25 30 35 40 45 0 20 40 60 80 100 120
Time(s)
Time(s)
Time(s)
Figure 8: FlowVisor’s bandwidth isolation prevents CBR traffic from starving TCP, and message throttling and new
flow message rate limiting prevents CPU starvation.
6 Deployment Experience
To provide evidence that sliced experimental traffic can
indeed co-exist with production traffic, we deployed
FlowVisor on our production network. By “production”,
we refer to the network that the authors rely on to read
their daily email, surf the web, etc. Additionally, six
other campuses are currently using the FlowVisor as part
of the GENI “meso-scale” infrastructure. In this section,
we describe our experiences in deploying FlowVisor in
our production network, its deployment in other cam-
puses, and briefly describe the experiments that have run
on the FlowVisor.
6.1 Stanford Deployment
At Stanford University, we have been running FlowVi-
sor continuously on our production network since June
4th, 2009. Our network consists of 25+ users, 5 NEC
IP8800 switches, 2 HP ProCurve 5400s, 30 wireless ac-
cess points, 5 NetFPGA [15] cards acting as OpenFlow
switches, and a WiMAX base station. Our physical
network is effectively doubly sliced: first by VLANs
and then by the FlowVisor. Our network trunks over
10 VLANs , including traffic for other research groups,
but only three of those VLANs are OpenFlow-enabled.
Of the three OpenFlow VLANs, two are sliced by
FlowVisor. We maintain multiple OpenFlow VLANs
and FlowVisor instances to allow FlowVisor develop-
ment without impacting production traffic.
For each FlowVisor-sliced VLAN, all network de-
vices point to a single FlowVisor instance, running on
a 3.0GHz quad-core Intel Xeon with 2 GB of DRAM.
For maximum uptime, we ran FlowVisor from a wrap-
per script that instantly restarts it if it should crash. The
FlowVisor was able to handle restarts seamlessly because
it does not maintain any hard state in the network. In
our production slice, we ran NOX’s routing module to
perform basic forwarding in the network. We will pub-
lish our slicing administration tools and debugging tech-
niques.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
6.2 Deploying on Other Networks
As part of the GENI “meso-scale” project, we also de-
ployed FlowVisor onto test networks on six university
campuses, including University of Washington, Wis-
consin University, Princeton University, Indiana Univer-
sity, Clemson University, Rutgers University. In each
network, we have a staged deployment plan with the
eventual goal of extending the existing OpenFlow and
FlowVisor test network to their production networks.
Each network runs its own FlowVisor. Recently, at the
8th GENI Engingering Conference (GEC), we demon-
strated how slices at each campus’s network could be
combined with tunnels to create a single wide-area net-
work slice. Currently, we are in the process of extending
the FlowVisor deployment into two backbone networks
(Internet2 and National Lambda Rail), with the eventual
goal of creating a large-scale end-to-end sliceable wide
area network.
6.3 Slicing Experience
In our experience, the two largest causes of network
instability were unexpected interactions with other de-
ployed network devices and device CPU exhaustion.
One problem we had was interacting with a virtual IP
feature of the router in our building. This feature allows
multiple physical interfaces to act as a single, logical in-
terface for redundancy. In implementation, the router
would reply to ARP requests with the MAC address of
the logical interface but source packets from any of three
different MAC addresses corresponding to the physical
interfaces. As a result, we had to revise the flowspace as-
signed to the production slice to include all four MAC ad-
dresses. Another aspect that we did not anticipate is the
amount of broadcast traffic emitted from non-OpenFlow
devices. It is quite common for a device to periodi-
cally send broadcast LLDP, Spanning Tree, and other
packets. The level of broadcast traffic on the network
made debugging more difficult and could cause loops if
our OpenFlow-based loop detection/spanning tree algo-
375
376
rithms did not match the non-OpenFlow-bases spanning
tree.
Another issue was the interaction between OpenFlow
and device CPU usage. As discussed earlier (§ 4.4), the
most frequent form of slice isolation violations occurred
with device CPU. The main form of isolation violation
occurred when one slice would insert a forwarding rule
that could only be handled via the switch’s slow path and,
as a result, would push the CPU utilization to 100%, pre-
venting slices from updating their forwarding rules. We
also found that the cost to process an OpenFlow message
varied significantly by type and by OpenFlow implemen-
tation particularly with stats requests, e.g., the OpenFlow
aggregate stats command consumed more CPU than an
OpenFlow port stats command, but not on all implemen-
tations. As part of our future work, we plan to compute
a per-message type costs to each OpenFlow request to
more precisely slice device CPU. Additionally, the up-
coming OpenFlow version 1.1 will add support for rate
limiting messages coming from the fast to slow paths.
6.4 Experiments
We’ve demonstrated that FlowVisor supports a wide va-
riety of network experiments. On our production net-
work, we ran four networking experiments, each in its
own slice. All four experiments, including a network
load-balancer [12], wireless streaming video [26], traffic
engineering, and a hardware prototyping experiment [9],
were built on top of NOX [11]. As part of the 7th GENI
Engingeering Conference, each of the seven campuses
demonstrated their own, locally designed experiments,
running in a FlowVisor-enabled slice of the network.
Our hope is that the FlowVisor will continue to allow
researchers to run novel experiments in their own net-
works.
7 Related Work
There is a vast array of work related to network exper-
imentation in both controlled and operational environ-
ments. Here we scratch the surface by discussing some
of the more recent highlights.
The community has benefited from a number of
testbeds for performing large-scale experiments. The
two most widely used are PlanetLab [21] and Emu-
lab [25]. PlanetLab’s primary function has been that of
an overlay testbed, hosting software services on nodes
deployed around the globe. Emulab is targeted more
at localized and controlled experiments run from arbi-
trary switch-level topologies connected by PCs. Shad-
owNet [3] exposes virtualization features of specific
high-end routers, but does not provide per-flow forward-
ing control or user opt-in. VINI [1], a testbed closely
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
affiliated with PlanetLab, further provides the ability for
multiple researchers to construct arbitrary topologies of
software routers while sharing the same physical infras-
tructure. Similarly, software virtual routers offer both
programmability, reconfigurability, and have been shown
to manage impressive throughput on commodity hard-
ware (e.g. [5]).
In the spirit of these and other testbed technologies,
FlowVisor is designed to aid research by allowing mul-
tiple projects to operate simultaneously, and in isolation,
in realistic network environments. What distinguishes
our approach is that we slice the hardware forwarding
paths of unmodified commercial network gear.
Supercharged PlanetLab [23] is a network experimen-
tation platform designed around CPUs and NPUs (net-
work processors). NPUs can provide high performance
and isolation while allowing for sophisticated per-packet
processing. In contrast, our work forgoes the ability
to perform arbitrary per-packet computation in order to
work on unmodified hardware.
VLANs [4] are widely used for segmentation and iso-
lation in networks. VLANs slice Ethernet L2 broadcast
domains by decoupling virtual links from physical ports.
This allows multiple virtual links to be multiplexed over
a single virtual port (trunk mode), and it allows a sin-
gle switch to be segmented into multiple, L2 broadcast
networks. VLANs use a specific control logic (L2 for-
warding and learning over a spanning tree). FlowVisor,
on the other hand, allows users to define their own con-
trol logic. It also supports a more flexible method for
defining the traffic that is in a slice, and the way users
opt in. For example, with FlowVisor a user could opt-in
to two different slices, whereas with VLANs their traffic
would all be allocated to a single slice at Layer 2.
Perhaps the most similar to FlowVisor is the Prospero
ATM Switch Divider Controller [24]. Prospero uses a
hardware abstraction interface, Ariel, to allow multiple
control planes to operate on the same data plane. While
architecturally similar to our design, Prospero slices in-
dividual ATM switches where FlowVisor has a central-
ized view and can thus create a slice of the entire net-
work. Further, Ariel provides the ability to match on
ATM-related fields (e.g., VCI/VPI) where OpenFlow can
match on any combination of 12-fields spanning layers
one through four. This additional capability is critical
for our notion of flow-level opt-in.
8 Trade-offs and Caveats
The Flow Visor approach is extremely general—it simply
states that if we can insert a slicing layer between the
control and data planes of switches and routers, then we
can perform experiments in the production network. In
USENIX Association
USENIX Association
principle, the experimenter can exploit any capability of
the data plane, so long as it is made available to them.
Our prototype of FlowVisor is based on OpenFlow,
which makes very few of the hardware capabilities
available—which limits the flexibility. Most switch and
router hardware can do a lot more than is exposed via
OpenFlow (e.g. dozens of different packet scheduling
policies, encapsulation into VLANs, VPNs, GRE tun-
nels, MPLS, and so on). OpenFlow makes a trade-off:
it only exposes a lowest common denominator that is
present in all switches in return for a common vendor-
agnostic interface. So far, this minimal set has met the
needs of early experimenters—there appears to be a ba-
sic set of primitive “plumbing” actions that are suffi-
cient for a wide array of experiments, and over time we
would expect the OpenFlow specification to evolve to be
“just enough”, like the RISC instruction set in CPUs. In
addition to the diverse set of experiments we have cre-
ated, others have created experiments for data center net-
work schemes (such as VL2 and Portland), new routing
schemes, home network managers, mobility managers,
and so on.
However, there will always be experimenters who
need more control over individual packets. They might
want to use features of the hardware not exposed by
OpenFlow; or they might want full programmatic con-
trol, not available in any commercial hardware. The first
case is a little easier to handle, because a switch or router
manufacturer can expose more features to the experi-
menter if they choose, either by vendor-specific exten-
sions to OpenFlow and FlowVisor, or by allowing flows
to be sent to a logical internal port that, in turn, processes
the packets in a pre-defined box-specific way.”
But if an experiment needs a way to modify packets
arbitrarily, the researcher needs a different box. If the
experiment calls for arbitrary processing in novel ways at
every switch in the network, then OpenFlow is probably
not the right interface, and our prototype is unlikely to
be of much use. If the experiment only needs processing
at some parts of the network (e.g. to do deep packet in-
spection, or payload processing) then the researcher can
route their flows through some number of special middle-
boxes or way-points. The middle-boxes could be conven-
tional servers, NPUs [23], programmable hardware [15],
or custom hardware. The good thing is that these boxes
can be placed anywhere, and the flows belonging to a
slice can be routed through them - including all the flows
from users who opt in. In the end, the value of Flow Visor
to the researcher will depend on how many middle-boxes
the experiment needs to be realistic—just a few and it
may be worth it; if it needs hundreds or thousands then
FlowVisor is providing very little value.
For example, this is how some OpenFlow switches implement
VPN tunnels today.
A second limitation of our prototype is the ability to
create arbitrary topologies. If a physical switch is to ap-
pear multiple times in a slice’s topology (i.e. to create a
virtual topology larger than the physical topology), there
is currently no standardized way to do this. The hardware
needs to allow packets to loop back, and pass through the
switch multiple times. In fact, most—but not all—switch
hardware allows this. At some later date we expect this
will be exposed via OpenFlow, but in the meantime it
remains a limitation.
9 Conclusion
Put bluntly, the problem with testbeds is that they are
testbeds. If we could test new ideas at scale, with real
users, traffic and topologies, without building a testbed,
then life would be much simpler. Clearly this isn’t the
case today: testbeds need to be built, maintained, and
are expensive to deploy at scale. They become obsolete
quickly, and many university machine rooms have out-
dated testbed equipment lying around unused.
By definition, a testbed is not the real network: there-
fore, we try to embed testbeds into the network by slicing
the hardware. This paper described our first attempt to-
wards embedding a testbed in the network. While not
yet bullet-proof, we believe that our approach of slicing
the communication between the control and data planes
shows promise. Our current implementation is limited to
controlling the abstraction of the forwarding element ex-
posed by OpenFlow. We believe that exposing more fine-
grained control of the forwarding elements will allow
us to solve the remaining isolation issues (e.g., device
cpu)—ideally with the help of the broader community. If
we can perfect isolation, then several good things hap-
pen: researchers could validate their ideas at scale and
with greater realism, the industry could perform safer
quality assurance of new products, and finally, network
operators could run multiple versions of the networks in
parallel, allowing them to roll back to known good states.
Acknowledgments
We would like to thank Jennifer Rexford, Srinivasan
Seetharaman, our shepherd Randy Katz, and the anony-
mous reviewers for their helpful comments and insight.
References
[1] A. Bavier, N. Feamster, M. Huang, L. Peterson, and
J. Rexford. In vini veritas: realistic and controlled
network experimentation. In SIGCOMM ’06, pages
3-14, New York, NY, USA, 2006. ACM.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 710) 377
378
[2]
BCM88130 - 630-Gbps High-Performance
Packet Switch Fabric. http://www.
broadcom.com/products/Switching/
Carrier-—and-Service-Provider/BCM88130.
X. Chen, Z. M. Mao, and J. V. der Merwe. Shad-
ownet: A platform for rapid and safe network evo-
lution. In Proceedings of USENIX Anual Technical
Conference (USENIX’09). USENIX, 2009.
L. S. Committee. leee802.1q - ieee standard for lo-
cal and metropolitan area networksvirtual bridged
local area networks. IEEE Computer Society, 2005.
N. Egi, M. Hoerdt, L. Mathy, F. H. Adam Green-
halgh, and M. Handley. Towards High Performance
Virtual Routers on Commodity Hardware. In ACM
International Conference on emerging Networking
EXperiments and Technologies (CoNEXT), Decem-
ber 2008.
FIRE - Future Internet Research & Experimen-
tation. http://cordis.europa.eu/fp7/ict/
fire/.
IETF ForCes. http://www.ietf.org/dyn/wg/
charter/forces-—charter.html.
GENI.net Global Environment for Network Inno-
vations. http: //www.geni.net.
G. Gibb, D. Underhill, A. Covington, T. Yabe, and
N. McKeown. OpenPipes: Prototyping high-speed
networking systems. In ’Proc. ACM SIGCOMM
Conference (Demo)”, Barcelona, Spain, 2009.
The gigabit testbed initiative. http: //www.cnri.
reston.va.us/gigafr/.
N. Gude, T. Koponen, J. Pettit, B. Pfaff, M. Casado,
N. McKeown, and S. Shenker. NOX: Towards and
operating system for networks. In ACM SIGCOMM
Computer Communication Review, July 2008.
N. Handigol, S. Seetharaman, M. Flajslik, N. McK-
eown, and R. Johari. Plug-n-Serve: Load-
Balancing Web Traffic using OpenFlow. In ACM
SIGCOMM Demo, August 2009.
Advanced traffic management guide. HP ProCurve
Switch Software Manual, March 2009.
procurve.com.
www.
T. V. Lakshman and D. Stiliadis. High-speed
policy-based packet forwarding using efficient
multi-dimensional range matching. In Proceedings
of the ACM SIGCOMM ’98, pages 203-214, New
York, NY, USA, 1998. ACM.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
[15]
[17
sy
[18
“4
oo
—
\o
=
[20
=
[21
ry
[22
4
[23
“4
[24]
[25
“4
[26]
J. W. Lockwood, N. McKeown, G. Watson,
G. Gibb, P. Hartke, J. Naous, R. Raghuraman, and
J. Luo. Netfpga—an open platform for gigabit-rate
network switching and routing. In MSE ’07: Pro-
ceedings of the 2007 IEEE International Confer-
ence on Microelectronic Systems Education, pages
160-161, 2007.
N. McKeown, T. Anderson, H. Balakrishnan,
G. Parulkar, L. Peterson, J. Rexford, S. Shenker,
and J. Turner. OpenFlow: enabling innovation
in campus networks. ACM SIGCOMM Computer
Communication Review, 38(2):69-74, April 2008.
Ns2 network simulator. http://www.isi.edu/
nsnam/ns/.
The OpenFlow Switch Consortium. http://www.
openflowswitch.org.
Opnet technologies. http: //www.opnet.com/.
R. Pang, M. Allman, M. Bennett, J. Lee, V. Paxson,
and B. Tierney. A first look at modern enterprise
traffic. In ACM Internet Measurement Conference,
2005.
An open platform for developing, deploying, and
accessing planetary-scale services. http://www.
planet-lab.org/.
J. Touch and S. Hotz. The x-bone. In Proc. Global
Internet Mini-Conference / Globecom, 1998.
J. S. Turner and et al. Supercharging planetlab: a
high performance, multi-application, overlay net-
work platform. In SIGCOMM ’07, pages 85-96,
New York, NY, USA, 2007. ACM.
J. E. van der Merwe and I. M. Leslie. Switchlets
and dynamic virtual atm networks. In Proceed-
ings of the fifth IFIP/AIEEE international symposium
on Integrated network management V : integrated
management in a virtual world, pages 355-368,
London, UK, UK, 1997. Chapman & Hall, Ltd.
B. White and J. L. et al. An integrated experimental
environment for distributed systems and networks.
In Proc. of the Fifth Symposium on Operating Sys-
tems Design and Implementation, pages 255-270,
Boston, MA, Dec. 2002. USENIX Association.
K.-K. Yap, T.-Y. Huang, M. Kobayashi, M. Chan,
R. Sherwood, G. Parulkar, and N. McKeown.
Lossless Handover with n-casting between WiFi-
WiMAX on OpenRoads. In ACM Mobicom
(Demo), 2009.
USENIX Association
USENIX Association
Building Extensible Networks with Rule-Based Forwarding
Lucian Popa* Norbert Egi*
Abstract
We present a network design that provides flexible
and policy-compliant forwarding. Our proposal centers
around a new architectural concept: that of packet rules.
A tule is a simple if-then-else construct that describes
the manner in which the network should — or should not
— forward packets. A packet identifies the rule by which
it is to be forwarded and routers forward each packet
in accordance with its associated rule. Each packet rule
is certified, guaranteeing that all parties involved in
forwarding a packet agree with the packet’s rule. Packets
containing uncertified rules are simply dropped in the
network. We present the design, implementation and
evaluation of a Rule-Based Forwarding (RBF) archi-
tecture. We demonstrate flexibility by illustrating how
RBF supports a variety of use cases including content
caching, middlebox selection and DDoS protection.
Using our prototype router implementation we show that
the overhead RBF imposes is within the capabilities of
modern network equipment.
1 Introduction
A central component of a network design is its forward-
ing architecture that determines the manner in which
packets are transported between two endpoints. Today’s
Internet offers users a simple forwarding model: a user
hands the network a packet with a destination address
and the network makes a best-effort attempt to deliver
the packet to the destination. Although simple, this archi-
tecture is also fairly limited and there have been repeated
calls to extend the Internet’s forwarding architecture for
greater flexibility—allowing, for example, the user to se-
lect the path his packets should traverse [20, 44, 47, 49]
or to specify whether packets can/should be processed
by middleboxes and active routers [47, 49, 29, 48, 25].
Achieving a flexible forwarding architecture has thus
been a long-held, if elusive, goal of Internet research
[47, 49, 29, 20, 48, 25, 40]. Our work in this paper shares
this goal. Our point of divergence from prior efforts
starts with the observation that forwarding flexibility is
inherently coupled with issues of policy.
Our thesis is that achieving flexibility is not just a
“University of California, Berkeley
TICSI, Berkeley
Lancaster University
’Intel Labs, Berkeley
Sylvia Ratnasamy? Ion Stoica*
matter of augmenting packets with more expressive
forwarding directives that routers execute. Rather, in ad-
dition, for each forwarding directive that enhances flex-
ibility, the parties involved in forwarding should be able
to set policies that constrain that directive. By the policy
of entity A (host, middlebox operator or ISP) we refer to
the decision whether to approve or reject a forwarding
directive based on A’s business or technical goals. By
forwarding directive we refer to instructions provided by
endpoints to routers and middleboxes on how to forward
their packets. For example, a forwarding directive could
specify that sender S can forward its packets through
middlebox M before reaching destination D. An example
of policy would be M refusing to accept packets from S.
To better illustrate our thesis, consider its application to
the Internet. Since the main forwarding directive in IP is
for sender S to send packets to destination D, D should be
able to specify that the traffic from S should not reach it,
i.e., either by explicitly allowing or denying packets from
D. Unfortunately, IP does not provide such functionality,
effectively leaving the end-hosts vulnerable to DoS
attacks. Unsurprisingly, this lack of functionality has
been identified as one of the main security vulnerabilities
of the Internet, and several solutions have been proposed
to address this limitation [51, 52, 21, 32, 37, 22].
Of course, forwarding directives and policies are only
as good as the ability of the network to enforce them and
to guarantee their authenticity. What complicates policy
enforcement is the involvement of multiple parties in
achieving the packet’s flexible behavior—the network
service providers along the path, potential middlebox
operators and, of course, the source and destination. As
such, the network must ensure that a packet’s forwarding
directive complies with the policies of all parties in-
volved. In our previous middlebox example, the network
must ensure that M is willing to relay packets from S
to D. If M does not approve, the network should simply
drop the packets before reaching M.
In this paper, we propose a new rule-based forwarding
architecture, RBF, that treats flexibility and policy
enforcement as equal design goals. RBF is based on a
new architectural concept — that of packet rules. In RBF,
instead of sending packets to a destination (IP) address,
end-hosts send packets to a rule. Rules are created by
destinations. A sender fetches the destination’s rule from
a DNS-like infrastructure and inserts it in the packets
sent to that destination.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) 379
380
A tule is a simple if-then-else construct that
describes the manner in which the network should — or
should not — forward packets. For example, a destination
A can receive packets only from source S using the rule:
Ra: if (pkt.source # S) drop pkt
Or a mobile client B might route certain video content
through a 3rd-party transcoding proxy with:
Rp: if (pkt.URL = hulu.com) sendPktTo trnscdPrxy
The above examples are anecdotal (we present precise
syntax and additional examples in §3) but serve to
illustrate how destinations can control and customize
how the network forwards their packets in a manner not
easily accommodated by current IP. In effect, with rules,
a receiving host must specify both which packets it is
willing to receive as well as how it wants these packets
forwarded and processed by the network.
The rule-based architecture we develop offers the
following properties:
Rules are mandatory: routers drop packets without
rules
Rules are provably authorized: all recipients (end-
hosts, middleboxes and/or routers) named in the rule
must explicitly agree to receive the associated packet(s).
Routers, middleboxes and end-users can verify a rule’s
authorization.
Rules are provably safe: rules cannot exhaust net-
work resources; e.g., rules cannot compromise or corrupt
routers nor cause forwarding loops.
Rules allow flexible forwarding: rules are a (con-
strained) program that allows a user to “customize” how
the network forwards its packets.
The first two properties assist in policy enforcement by
ensuring a packet is only forwarded if explicitly cleared
by all recipients (i.e., if it conforms with the policies of
all recipients) specified in the rule. Since RBF defines
policies on rules, any recipient will have the ultimate say
on whether to accept any rule that contains forwarding
directives sending packets to it. Since all forwarding
directives are encoded into rules, we achieve our goal of
enabling any entity affected by a forwarding directive to
constrain that directive.
The third property ensures rules cannot be (mis)used to
attack the network itself. As we shall show, the last prop-
erty provides flexibility since users can give the network
fine-grained instructions on how to handle their packets,
enabling: explicit use of in-network functionality at
middleboxes and routers, loose path forwarding, multi-
path forwarding, anycast, multicast, mobility, filtering of
undesired senders/ports/protocols, recording of on-path
information, etc. In the remainder of this paper, we
present the design, implementation and evaluation of a
forwarding architecture that meets the above properties.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
RBF relates to an extensive body of work on both for-
warding flexibility and policy enforcement. We discuss
related work in detail later in this paper and here only
note that, at a high level, we believe what distinguishes
RBF is its focus on simultaneously supporting flexibility
and the multi-party policy requirements that such
flexibility implies. As we shall see, this goal leads us to
a design that differs significantly from prior proposals.
Finally, we note up-front that RBF is more complex
than the existing IP forwarding architecture, which is
frequently cited for its simplicity. In addition, RBF
relies on strong assumptions such as anti-spoofing, the
existence of rule-certifying authorities and a DNS-like
infrastructure to distribute rules. The gain, relative to
today’s IP forwarding, is significantly improved flexibil-
ity and security; we posit that the greater complexity of
our solution is a perhaps inevitable consequence of this
richer service model.
2 Design Rationale and Overview
We start with the goal of network flexibility and allowing
users control over how the network processes their pack-
ets. The abstraction that perhaps best supports flexibility
is simply that of a program, leading to an architecture
where users write packet-processing programs that
routers execute. This vision of code-carrying packets is,
of course, the cornerstone of active networking [48, 50]
and we borrow this as our starting point in designing
RBF. However, as we shall see, RBF severely dials
back on the full-fledged generality of the original active
networks’ vision to arrive at a significantly simpler and
safer architecture.
Rules are thus a form of program. The challenge then is
to appropriately constrain these programs/rules to ensure
that they cannot harm the network or other hosts. The key
insight behind RBF is that these constraints must extend
along two dimensions. First, rules must be safe, i.e., guar-
anteed not to corrupt or exhaust network resources. In
addition, however, we must constrain rules to respect
the policies of all stakeholders involved—soutce, desti-
nation, middleboxes and ISPs. This latter requirement is
unique and yet critical to networking contexts but was
under appreciated in early active networking proposals.
To address policy safety, RBF incorporates two key
design decisions:
(D1) Layering: we believe network operators will be
unwilling to relinquish control of route discovery and
computation and hence we layer RBF above current IP
forwarding and do not allow rules to modify the IP-layer
forwarding information base (FIB).
(D2) Verifiable stakeholder agreement: we require
that a rule be authorized by all entities it explicitly
names (e.g., destination, middleboxes or routers). This
ensures agreement of the stakeholders’ policies with
USENIX Association
USENIX Association
the rule’s intent; in particular it also ensures that rules
cannot violate ISPs’ routing policies, since providers
must explicitly agree to have their routers named in
rules. To achieve this property, in RBF rules are certified
by trusted third parties, which in turn gather proofs of
policy compliance from each of the rule participants.
To address rule safety, we impose strict constraints
on rule syntax, such that safety can be verified through
simple static analysis:
(C1) Rules cannot directly modify router state. This
avoids corruption of router state. However, this can be
a limiting restriction, particularly to network operators
who wish to expose in-network services such as caching
or monitoring to end users. To accommodate this, RBF
allows operators to deploy specialized packet-processing
functions at their routers and allows rules to invoke these
functions. Such “router-defined functions” do allow rules
to update router state, but only indirectly via code in-
stalled, and hence presumably trusted, by operators. This
model for router-defined functions thus represents a mid-
dle ground in the tradeoff between flexibility and safety.
(C2) The rule “instruction set’ is limited to only four
possible action statements: (a) forward the packet to
the underlying IP layer, (b) invoke a router-defined
function, (c) modify the packet header and (d) drop
the packet, plus conditionals that determine whether an
action should be taken based on reading packet headers
and router state. Note that there is no action that allows
backward jumps across rule statements. This prevents
looping or resource exhaustion at routers and ensures
execution time is linear in program size.
The above constraints represent a stark departure from
the rich generality of the active networks vision. Indeed,
rules are more a sequence of packet steering directives,
rather than a full-fledged program. The benefit is ver-
ifiable rule and policy safety. Moreover we find that,
despite these constraints, rules suffice to express a wide
variety of forwarding behaviors as we will later illustrate.
2.1 Architecture Overview and Assumptions
We now provide a brief overview of the main compo-
nents and assumptions of an RBF architecture. Figure |
illustrates the forwarding architecture of an RBF-enabled
router. On receiving a packet, the router hands it to the
rule forwarding engine, which processes the packet’s
rule. Such processing may involve reading router state
that the network operator has opted to expose; we term
such state router attributes. Based on information in the
packet header (packet attributes) and router attributes,
the rule forwarding engine may update the packet’s
attributes (including its destination), invoke router
functions, drop the packet and/or hand the packet to the
underlying IP forwarding engine. Recall that for safety
reasons the rule is not allowed to update router state.
bo Specialized
s forwarding
& functions
(optional)
Rule
Forwarding
IP
Forwarding
Router
Attributes
Figure 1: RBF router and rule forwarding
The design of a rule-based architecture involves the
design of rules themselves as well as the surrounding
infrastructure required to support the distribution,
processing and securing of rules. Consequently, the RBF
architecture consists of four main components:
e The specification of packet rules — their syntax, packet
encoding, constraints on what rules can and cannot do.
e Certificate authorities called Rule Certification Enti-
ties (RCEs) that certify rules after checking that they
are well formed, and that every destination specified
in the rule agrees with (i.e., has signed) the rule.
e Modified IP routers that verify rule certificates and
process packets as described above.
e A modified DNS infrastructure that either directly
resolves a host D’s domain name to D’s rule, or
resolves D’s domain name to another rule resolution
server which in turn provides D’s rule.
Assumptions: RBF builds on three major assumptions.
First, RBF assumes the existence of an anti-spoofing
mechanism. This is required because rules may use
source and destination IP addresses in their decision pro-
cess and hence addresses must be legitimate, otherwise
policy compliance cannot be enforced.! In this paper we
assume the use of ingress filtering, although RBF can
accommodate alternate solutions, e.g., Passports [36].
The rationale behind our choice of ingress filtering is
described in 84.
Second, we assume routers know the public keys of
RCEs and can thus verify rule certificates. We assume
the number of RCE organizations is relatively small and
these keys can be statically configured at routers, akin to
‘Note that any solution for blocking undesired traffic inside
the network requires a way to identify sources. Anti-spoofing
identifies users based on their addresses. An alternative, is to
identify users by their access path [51, 52], but this approach
ties communications to a specific path restricting flexibility
(e.g., for mobility, traffic engineering, multi-path forwarding).
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI’10) 381
382
how browsers today are configured with the list of major
certificate authorities.” Note that although we assume a
small number of RCE organizations, we envisage each
organization will run geographically replicated instances
of their service for improved scalability and robustness.
Finally, we assume that the rule resolution infras-
tructure (whether DNS or the resolution servers the
DNS points to) is well provisioned, akin to how major
Internet services (Google, DNS, Amazon) operate today,
relying on engineering approaches such as maintaining
a presence at major ISPs, IP anycasting, bandwidth pro-
visioning, and so forth. As described in §4, we make this
assumption to protect against “denial of rule” attacks.
Clearly, these assumptions are significant and may im-
pede an immediate deployment of RBF in practice. And
even with these assumptions, the resulting RBF design is
far from trivial (for this reason, we in fact offload some
of the details to an extended technical report [42]). How-
ever, we hope through the design presented in this paper
to start a focused discussion about how best to practi-
cally introduce flexibility and security into the Internet
and about what set of primitives routers must support to
achieve this goal. In this paper we present one solution to
this problem; in §10 we succinctly discuss the arguments
that have led us to these specific assumptions and design.
3 The RBF Data Plane
In this section we describe the key components of the
RBF data plane: rule syntax and how routers verify and
execute rules. We then present examples of how rules
are used.
3.1 Rule Specification
RBF represents a rule as a sequence of actions that can
be conditioned by if-then-else instructions:
if (<CONDITION>) ACTION1
else ACTION2
Conditions are comparison operators applied to packet
and router attributes. An action can be one of:
1. forward the packet to the underlying IP engine;
2. invoke a local function available at the router;
3. update the value of the packet attributes;
4. drop the packet.
Packet attributes include the standard IP header five-tuple
(IP addresses, ports, protocol type) and, optionally, a
number of custom attributes with user-defined semantics.
For simplicity, RBF does not allow rules to dynamically
add new attributes. Router attributes may include, for
example, the router’s IP address, AS number, link
congestion levels, and flags indicating whether the router
Some may regard this model of security unsatisfactory, we
discuss alternatives to this deployment in §4.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
implements a specific function (e.g., a rule can check
router.local_cache to discover whether the router
maintains a local content cache). Rules are allowed to
update packet attributes, but not router attributes.
Each rule has an associated /ease that ensures the rule
can only be used for a limited period of time (84.3).
Also, every rule has an identifier (ID) defined as the con-
catenation of a hash of the rule owner’s public key and
an index unique to the owner, hash(PK_owner):index. In
Section 7 we present an optimization to reduce packet
overhead and identify most rules by using a hash over
their content. This optimization can be used in the
common case when there is no need for multiple rules
with the same identifier; for example, mobile hosts may
require different rules with the same identifier (see §3.4).
The following is an example of a rule that forwards
a packet to destination D via a waypoint router R1;
a packet attribute visitedR1 indicates whether the
packet has already visited R1:
R_D:
if (packet. visitedR1 == FALSE) //from src. to Ri
if (router.address != R1)
sendto R1
else packet.visitedR1 = TRUE //to D
if (packet. visitedR1 )
sendto D
where sendto involves setting the IP destination ad-
dress to D and then handing the packet to the underlying
IP forwarding engine (assuming, of course, that D is
not the local address). Rule execution terminates at a
sendto or drop action; the packet is dropped if the
rule does not arrive at an explicit sendto. Finally,
rules can invoke local functions at the router; after the
invocation the packet is returned to the forwarding layer.
3.2. Distributing Rules to Routers
To forward a packet, a router must first obtain its rule.
There are two potential approaches: (1) rules are carried
in packets, (2) routers use an out-of-band mechanism to
obtain rules. In RBF, we choose to carry rules in packets
since the second approach would require complex rule
distribution and storage protocols, and would incur extra
delays in communication setup (in fact this approach
would likely require special “rule-less” traffic to install
rules). The tradeoff is higher overhead on the data path
as rules increase packet size and routers must verify each
packet’s certificate; our evaluation in §7 suggests this
overhead is acceptable given the capabilities of modern
network equipment.
A packet with source S and destination D must include
a destination rule, RD, which is the rule specified and
owned by D. In addition, a packet may include a return
rule; this is the rule specified and owned by S and is
used for return traffic from D to S.
USENIX Association
USENIX Association
3.3. Rule Verification
As mentioned earlier, rules are certified by a Rule Cer-
tification Entity (RCE) and all packets carry a signature
that routers must verify. The verification load at routers
is eased by two factors. First, only routers at trust bound-
aries need to verify rules. Second, routers can cache ver-
ification results by maintaining a hash of the rule and its
signature. With caching, the full signature verification is
only required for the first packet forwarded on a new rule
(as long as the verification result is cached). Thus, verifi-
cations can be limited only to border routers and, assum-
ing a large enough cache, the verification rate is given by
the arrival rate of packets with new rules. By contrast,
the signature length adds to the overhead of every packet.
Different cryptographic solutions offer different trade-
offs between signature length, signing time (incurred
only at RCEs), verification time (incurred at routers)
and security. Our current RBF design assumes Elliptical
Curve Cryptography (ECC) because ECC signatures are
shorter than RSA ones, while exhibiting similar security
properties. At the same time, verification time in ECC
is typically longer than RSA’s. However, in practice
verification can be accelerated using ASIC-based im-
plementations or dedicated specialized co-processors.
Such implementations are already commercially avail-
able [5, 7, 8] and incorporated into network appliances
and routers. Furthermore, traffic measurements [4] show
that new flow arrivals represent less than 1% of the
link capacity on average and less than 5% of the total
number of packets, a volume that can be accommodated
using commercial ECC modules [5, 7] or recent research
proposals [53, 34]. We evaluate different signature
mechanisms briefly in § 7 and in greater detail in [42].
3.4 Examples of RBF usage
To illustrate the application of rules, we present a series
of example usage scenarios; the rule syntax in these
examples is largely identical to the high-level rule
language supported by our RBF prototype router (86),
with simplifications for readability as appropriate.
Port-based filtering: A web server, D, uses the following
simple rule to ensure it only receives packets on port 80:
R-filter_port:
if (packet.dst-port != 80) drop;
sendto D
Middlebox Support: In addition to accepting traffic di-
rectly on port 80, D might use the following rule to
route all other incoming traffic through a packet scrub-
ber [2, 6]. This functionality can be deployed either by
D’s provider (as a router function), or by a third party (at
a middlebox Scrb) as presented below:
’Note that Scrb can represent the address of a load
balancer used with several physical middleboxes.
R-mbox-port:
if (packet. dst_port == 80)
sendto D // directly to D
else
if (packet.scrubbed == FALSE) //before scrubber
if(router.address != Scrb)
sendto Scrb
else //at scrubber
packet.scrubbed = TRUE //mark scrubbed
invoke Scrb-_service // scrub
else
sendto D // after scrubber
Thus, similar to previous proposals [47, 49], RBF
provides explicit support for middleboxes such as
WAN optimizers, proxies, caches, encryption engines,
transcoders, SSL offloaders, intrusion detection, etc.
Secure Middlebox Traversal: In the previous example,
an attacker can directly send a packet with the attribute
values set so as to appear that the packet has already vis-
ited the middlebox. More generally, one should be able
to enforce that rule directives are respected when the rule
participants (sources, middleboxes) are not trusted.
One approach to protect against this behavior is to
leverage RBF’s assumption that sources cannot spoof
their addresses. More specifically, after each middlebox
the rule can verify that the packet has indeed been sent by
the required middlebox, since middleboxes/waypoints
need to set the (non-spoofable) source address attribute
in packets (for brevity we omit this in the presented
examples); see [42] for more details on this approach.
In an alternate approach, special cryptographic func-
tions deployed at middleboxes and destinations can be
used to create/verify proofs guaranteeing the packet has
visited the middlebox, as follows:
R_mbox-port_crypto:
if (packet. dst_port == 80)
sendto D // directly to D
else
if (packet.proven == FALSE)
if(router.address != Scrb) //before scrubber
sendto Scrb
else //at scrubber
if (packet .scrubbed == FALSE)
packet.scrubbed = TRUE
invoke Scrb-service //(1) scrub
else // scrubbed
packet.proven = TRUE
invoke Prove //(2) create proof
else // proven
if(router.address != D)
sendto D
else
invoke VerifyAndDeliver //check proof at D
In this example, the Prove function at the middlebox
signs the immutable part of the packet header and/or
payload, and adds this signature as an attribute to the
packet header. In turn, the VerifyAndDeliver
function at D checks the middlebox signature and, if the
check succeeds, delivers the packet to the end applica-
tion. Note that checking the signature requires that D
knows the public or shared key(s) of middlebox(es); for
efficiency, the middlebox could sign the hash chain of a
batch of packets.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) = 383
384
DoS Protection: To protect against DDoS attacks, a
server D can create a custom rule for each client that
drops packets from any source other than the client. By
controlling the number of rules active at a given time,
D controls the maximum number of active clients (each
rule has an associated lease period). An example of a rule
similar to a network capability [52, 51] is:
R-filter_src:
if (packet.source != requester -_IP)
drop;
...//rest of the rule
Similarly to capability based architectures [52, 51],
our solution is based on the premise that destinations
are able to grant rules on demand, and that any requester
can ask for a destination’s rule. In RBF, this task falls
to the rule resolution infrastructure and raises the pos-
sibility of a “denial of rule” attack on this infrastructure
(akin to denial-of-capability attacks in capability-based
systems[41]). We present the details of rule resolution
and discuss denial-of-rule attacks in §4.
Mobility: Host D changes its network IP address due
to physical movement. In RBF, D can continue an exist-
ing communication without having to re-establish it. To
achieve this, D creates a rule for the new address with the
same ID as the rule used in the existing communication,
and places it in the packet as the return rule.
Multicast: For security reasons, RBF does not support
packet replication, and thus multicast cannot be imple-
mented entirely at the RBF layer. Instead, multicast can
be implemented by invoking multicast functionality de-
ployed by ISPs at a subset of their routers; this function-
ality maintains (soft) state at routers to create a (reverse
path) multicast tree. This approach implements essen-
tially an overlay multicast solution, which leverages the
IP multicast functionality at on-path routers (see [42] for
details).
On-path Caching: Consider an ISP I that deploys
caching functionality at some of its (border) routers. A
web-service D can contract with I and use this function-
ality. For this purpose, D creates and publishes the fol-
lowing rule:
R-caching:
if (router.caching-available and
packet.crt_router != router.address)
packet.crt_router = router.address
invoke Caching
sendto D
where the crt_router attribute makes sure the
caching functionality is called just once at each
caching-enhanced router.
In this example, the caching functionality can decide
to respond to the requester directly and not forward
the packets further to D, which reduces latency for the
requester and traffic load at D. A similar rule can support
recent proposals for content-centric routing [35, 33].
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
Other Examples: Our technical report [42] provides ex-
amples of applying RBF to a range of additional ap-
plications, including: secure loose path forwarding [44,
40], multipath forwarding, network diagnostics, anycast,
reverse traceroute (path recording), delay-tolerant net-
working and even source control over middlebox or path
selection. Importantly, these individual examples can be
combined as needed. For example, a content distribution
network can distribute load among multiple sites using
anycast and, at the same time, protect its servers with on-
path IDS functionality provided by ISPs.
4 The RBF Control Plane
In RBF, ISPs provide their clients with rules to access
the local DNS server and a Rule Certification Entity
(RCE), which can certify clients’ rules. This information
can be provided through a modified DHCP service,
similar to the way ISPs or organizations provide the IP
address of DNS servers today.
In this section, we describe the RBF mechanisms for
rule creation and certification (§4.1), rule distribution
($4.2), lease enforcement ($4.3) and anti-spoofing (84.4).
4.1 Rule Creation and Certification
To receive traffic, a client must create a rule that allows
one or more sources to send traffic to it. Before distribut-
ing this rule, the client must ask an RCE to certify it.
RCE certification guarantees that rules obey the policies
of all stakeholders. In particular, certification guarantees
the following properties:
1. Every destination in the rule (i.e., any address that
appears as an argument of a sendto instruction) has
agreed to receive packets using that rule;
2. The operators providing router functions invoked by
the rule approve the rule behavior;
3. The rule cannot cause infinite loops;
4. The rule cannot bypass ISP routing policies.
A client can either create rules itself and directly ask
an RCE to certify these rules, or use a trusted DHCP-like
service to create and certify rules on its behalf. In the
remainder of this section we present the former case.
As described above, the ISP provides each client with
a rule to access an RCE that has a contract with the ISP.
The following example shows a possible rule that allows
a client D to access an RCE named C:
Rp—c: if (source == D) sendto C
Before certifying a rule, an RCE verifies that the rule
has been authorized by each destination that appears in
the rule. A client who has created a rule authorizes it by
simply signing the rule with its private key. A client that
appears in the rule as a destination, other than the rule’s
creator, will first verify that the content of the rule obeys
USENIX Association
USENIX Association
“Of
Packet Format: D IR, lease] yo, [IPpsKpl rt Cc
| Rule | | > fine
= iy
Payload FSC” i
Qa [R, lease] yc
Figure 2: Rule Certification
its policies before signing the rule. For example, an
intrusion detection box may verify that the destination
indeed belongs to a client allowed to use the service
(e.g., based on a contract between the client and the
provider of the intrusion detection service), a waypoint
router may verify that the final destination is allowed to
use source-routing, efc.
Let (Kp, K aD denote the (public, private) key pair
of client D, and let IP p be the IP address of D. To prove
to an RCE that the client signing the rule with private
key Ke indeed owns IP address IP p, client D sends
a certificate along with the signed rule that binds its
public key Kp and IP address IPp. This certificate is
signed by an entity T, i.e., [I Pp, Kolko where kK!
represents the private key of T. Clearly, the RCE must
trust entity T. In fact, in our solution we will assume that
T is itself an RCE.
Next, we present the rule certification process in detail,
initially for the case in which the rule has a single
destination, and then for the case in which the rule has
multiple destinations or waypoints/middleboxes.
Certify single-destination rules: Assume destination D
wishes to certify a rule R that forwards packets only to
its address IPp, e.g.,R: sendto IPp. Also, assume D
already has a rule Rp on which it can be reached by the
RCE C. D obtains this rule as part of the bootstrapping
process, which we discuss later.
Fig. 2 shows the certification of D’s rule, R, by C:
1. Host D signs rule R with its private key, and sends
it to C using rule Rp.c. In addition, D sends
the certificate binding its public key and address,
i.e., [I[Pp, Kol\qo Upon receiving this request, C
verifies the certificate as well as the signature of the
requested rule. These ensure that the request has been
made by the owner of Kp and that the requester is
also the owner of IP :p. In addition, C verifies that R
is well formed (see §5).
2. If rule verification succeeds, C signs the rule with its
private key and sends it back to D using the return
rule in its certification request, Rp. At this point, host
D can distribute rule R to other hosts directly (as a
return rule) or through DNS.
The certification procedure (Fig. 2) needs only to
guarantee the authenticity of the request. Since rules are
public, confidentiality is not a concern. Since the lease is
an absolute value (84.3), the only effect of replaying rule
requests is increased traffic at the RCE. The maximum
lease value that C can sign for a rule is negotiated
between D’s ISP and C. Furthermore, RCEs can limit the
number of clients contacting them and can limit each
user’s certification rate, as we discuss in this section.
Certify multiple destination rules: In this case, every
destination (i.e., any host, middlebox, or waypoint router
that appears as an argument of a sendto instruction) in
a rule must agree to receive packets on that rule, i.e., the
rule must respect its policies. In particular, every such
destination must sign the rule. One of the destinations, D,
collects the signatures of all the other destinations along
with their certificates binding their public keys to their
addresses. D then sends this information to its RCE. In
turn, the RCE verifies that all destinations in the rule
have signed the rule and sends the signed rule back to
D. The lease signed by the RCE has the minimum du-
ration between the requested lease and the leases of all
the certificates binding the addresses and the keys of the
participants.
Certify rules invoking functions: Operators providing
router functions can restrict which rules can invoke these
functions. The certification process is similar to certify-
ing multiple destination rules. The identifiers of func-
tions whose invocation requires authorization are repre-
sented as hashes of public keys. RCEs certify a rule con-
taining such an invocation only if the rule is signed with
the private key corresponding to the function identifier.
Bootstrapping: To certify rules, client D needs to (1)
know the rule to contact an RCE, C; (2) provide C with a
return rule to receive the certified rule; and (3) obtain the
certificate from a trusted authority that signs the binding
between D’s key (Xp and its address IPp. We assume the
ISP provides D with a rule to access an RCE C (similarly
to how ISPs today bootstrap clients’ access to the DNS).
Given this initial rule, we use a simple request-response
exchange between the client and the RCE to obtain both
the certificate binding the client’s IP address to its key as
well as its first rule. Due to space constraints, we refer
the reader to our extended technical report [42] for more
details on the bootstrapping process.
RCE load and availability: To control its certification
load, an RCE can rate-limit the number of certification
requests that it processes from each individual client.
Clients are identified by IP address; the anti-spoofing
mechanism prevents clients from impersonating each
other. Alternatively, clients can be identified by “person-
alized” rules provided by the ISP to the customer to ac-
cess the RCE; such rules may have a finer granularity
than the anti-spoofing mechanism. RCEs can indirectly
protect themselves against link-level DoS attacks by con-
trolling the number of clients under contract.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) 385
386
(a) Regular Distribution
Figure 3: Rule Distribution (solid lines = rule lookup pro-
cess; dashed lines = data communication; dotted lines = setup)
(b) DDoS protection
RCEs must be highly available to enable rule certifi-
cation at any time. RCEs can meet this requirement by
using multiple servers and multiple sites. ISPs and desti-
nations can protect themselves against RCE unavailabil-
ity by contracting with multiple RCEs.
RCE Key Distribution and Revocation: In this paper
we do not explore solutions for the distribution and revo-
cation of RCE keys to routers. Here, we simply mention
two possible approaches towards this goal. In one ap-
proach, RCE keys could be distributed and revoked using
DNSSEC. For example, in the txt or other RR type, one
DNS entry contains the number of RCEs and, for each
RCE, there is one DNS entry (based on its index such as
“ID24.rce”) that contains the RCE’s key. Routers period-
ically update the RCE keys. In another approach, RCEs
could be deployed along AS boundaries, such that each
AS would have its own RCE. This approach has the ad-
vantage that additional security can be enforced, e.g., the
trust in some RCEs can be restricted to their own address
ranges. Secure BGP could be used to distribute RCE keys
in this case, but at the expense of extra complexity.*
4.2 Rule Distribution
RBF uses an extended DNS infrastructure to distribute
tules, as illustrated in Fig. 3(a). The destination D creates
and certifies a rule for itself (step A) and inserts it into
the DNS (step B). A sender S that wants to contact D
looks up D’s name in the DNS; the DNS is extended
to return D’s rule rather than its address (step 1). After
obtaining a rule to D, S directly sends packets to D (step
2). Note that for practical purposes the rules of the DNS
root servers need to have long leases (to avoid tedious
reconfiguration or refresh protocols), as with today’s
long-lived addresses.
In Section 3.4 we pointed out that rules can be used
to block DDoS attacks. This relies on (1) the ability to
distribute customized rules to different senders (i.e., give
a sender S arule that drops all packets not generated by
S) and on (2) the ability to protect the rule distribution
itself from DoS attacks.
To protect against DDoS attacks, client D can contract
with a large entity E, and redirect its DNS entry to E,
by registering E’s rule under its DNS name. Fig. 3(b)
‘Note that DNSSEC could also potentially be used to
distribute keys when RCEs are deployed along AS boundaries.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
illustrates this approach. DNS will reply to a lookup
for D’s name with E’s rule (step 1). The DNS entry that
contains E’s rule must belong to a new type of DNS RR.
This new class of entries is returned directly to clients by
DNS resolvers. Upon a receipt of such an answer to its
DNS query, the requester will continue the DNS lookup
by contacting E (step 2). E rate-limits rule requests and
forwards them to D (step 3), thus protecting D from DoS
attacks. For the authorized requesters, D creates rules
(step 4) and replies back to the requesters (step 5). E
forwards requests to D conforming to a policy (see §3.4),
which can be updated by D at any time.
Note that some malicious users may still get their re-
quests forwarded by E and authorized by D. To alleviate
this attack, E can employ fair queuing across senders,
and D can blacklist known attackers at E. Such an ap-
proach offers a protection similar to network capabilities
that apply per-source fair queuing at routers [37].
4.3 Rule Leases
The lease is an expiration time stamp certified along
with the rule description. A router drops a packet if
its current time exceeds the rule expiration time. For
simplicity, in this paper we assume that all routers and
RCEs are synchronized via NTP [14] as recommended
by router manufacturers [19]. We present a solution that
does not rely on global clock synchronization in [42].
4.4 Anti-Spoofing Mechanism
If a source can spoof addresses on packets it sends, it can
send packets to a destination D even if the rule does not
allow it to, and in this way evade D’s policy. Moreover,
one can mount a DDoS attack by using a single rule
distributed by a malicious source to a set of colluders. To
address this problem, RBF can use a previously proposed
anti-spoofing mechanism. In this paper, we propose the
use of ingress filtering, which is already deployed by
over 75% of today’s ASes [23]. When deploying RBF,
RBF routers could also be used to apply ingress filtering.
Note that if malicious ASes do not apply ingress filter-
ing, DoS protection is not fully compromised as only
hosts in these ASes can launch attacks.
Instead of ingress filtering, RBF could leverage other
anti-spoofing mechanisms such as Passport [36]. How-
ever, Passport [36] requires a secure routing layer and
incurs extra overhead in packets.
The anti-spoofing mechanism requires middleboxes
and routers that change a packet’s destination address
also to change the packet’s source address attribute.
5 Security Analysis
The RBF design aims to achieve the following three
goals: (i) policy enforcement — ensure that the authorized
rules respect the policies of all participants (routers, mid-
dleboxes, destinations), and packets with unauthorized
USENIX Association
USENIX Association
Static
Analysis
Certifi- Lease Anti-
cation Spoofing
Mechanisms
Properties \
No Rule Forging x x
No Rule Tampering x
No Rule Evasion x x
Network Safety x x
Table 1: Properties and Defense Mechanisms
rules are dropped inside the network; (ii) rule enforce-
ment - rules cannot be used by malicous senders and, if
senders or rule participants are untrusted, respect of rule
directives can be enforced; and (iii) rule safety rules
cannot be used to attack the network. Next, we sum-
marize RBF’s security properties, the threat model and
assumptions under which they hold, and the mechanisms
that allow RBF to meet these goals. We present a detailed
analysis and proofs of RBF’s security properties in [42].
Assumptions: We assume that DNS resolution is secure,
that distribution of RCE keys to routers is secure, and that
RCEs are not malicious.
Attackers: An attacker in RBF can be any host, middle-
box, or router: sources can attempt to attack destinations
by forging, evading or tampering with their rules; des-
tinations can try to attack the network by creating rules
that waste resources and slow down routers; middleboxes
and routers can attempt both of these attacks.
Security Properties: We decompose the aforementioned
security goals into four specific desired properties:
1. No Rule Forging: A host S cannot manufacture a
rule that sends packets to another host D, unless D
explicitly agrees with this rule, i.e., destinations and
middleboxes control the creation of rules that send
traffic to them.
2. No Rule Tampering: Sources, routers and middle-
boxes cannot tamper with the destination’s rules.
3. No Rule Evasion: Host S cannot send packets to des-
tination D, if D’s rules do not accept packets from S.
4. Network Safety: A destination D cannot create
unsafe rules. In particular, D cannot create rules that
(a) cause infinite loops, (b) corrupt router state, (c)
DoS routers or RCEs, or (d) violate ISP policies.
Mechanisms and Defenses: RBF uses four mechanisms
to achieve the above properties: (1) rule certification, (2)
tule leases, (3) anti-spoofing, and (4) static analysis. Ta-
ble 1 summarizes which mechanisms serve to meet the
four security properties.
6 Implementation
This section describes our prototype RBF router and
rule compiler.
6.1 An RBF Rule Compiler
Our prototype offers users a high-level language largely
identical to the syntax used in this paper in which to write
rules. We wrote an RBF compiler in C++ that translates
this high-level language into a compact rule format
carried in packets. This compact format uses: 8B(ytes)
for public-key hashes, 3B for the user-local index, 3B to
identify the RCE, 3B to identify router-defined functions
that do not require approval to be invoked and 8B for
those that do, and 2B as the default RBF packet attribute
values.° For the lease we use an absolute expiration time
consisting of first 4B of the NTP format, with second-
level granularity and a wrap-around period of 136
years. For efficiency, we use variable-length encoding in
representing the internal rule structure. The maximum
rule description size is 256B in our implementation.
6.2 A Prototype RBF Router
Rationale: We implemented RBF forwarding using
Click [39] and RouteBricks [26]. Most commercial
routers implement packet processing using ASICs
or specialized network processors (NPs) rather than
general-purpose CPUs and, as such, our software-based
prototype is not entirely representative of currently de-
ployed routers. To a large extent, our choice of proto-
typing platform is borne of necessity since commercial
routers are closed. Beyond necessity, however, we be-
lieve a software-based prototype is valuable for mul-
tiple reasons. First, recent research [26, 31, 27] has
demonstrated that, with modern multi-core servers, it is
now possible to build high-speed software routers up to
edge and even core speeds. Secondly, while not directly
reusable, several aspects of our implementation archi-
tecture such as our approach to partitioning tasks across
multiple cores should apply to network processor-based
routers. Finally, several research [12, 28] and commercial
switches [3] augment ASIC-based switches with some
number of co-located general-purpose cores or servers
for greater flexibility in packet processing — our proto-
type architecture is directly applicable to such platforms.
Design requirements: We build our prototype in the
context of modern multi-core servers that incorporate
multiple processors or “sockets”, each with multiple
cores [17, 1]. As shown in Fig. 1, the software stack of an
RBF router includes the following key components: (1)
an IP forwarding module, (2) the rule execution engine,
and (3) some (possibly zero) number of specialized for-
warding function modules. All packets traverse the rule
execution and IP forwarding components, while different
subsets of packets may traverse one or more specialized
functions. In addition, the resources required to process
a packet may vary widely across functions; e.g., an en-
Our current prototype only supports this default size.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) 387
388
Kernel Kernel User level
+
a
©
—
eI
=
=
Socket 0
CPU Cores
Socket 1
Figure 4: Core Allocation Example in RBF Router
cryption function would use lots of CPU but little cache,
while a caching module may use more cache and less
CPU. At a high level, our design goal is to balance high
performance (i.e., making efficient use of resources) with
performance isolation, both across different functions,
and between functions and the rule execution engine
(i.e., sharing resources in a fair manner).
Approach: In its full generality, the above goal re-
quires contention-aware scheduling that simultaneously
takes into account the multiple resources (cores, various
caches, memory bandwidth, I/O bandwidth) for which
tasks might contend. For modern multi-core systems, this
is in itself an area of active research [24, 54] and be-
yond the scope of this paper. Instead, in our prototype,
we address the issue as follows. The IP forwarding mod-
ule and the rule execution engine are the central, most
critical, components of the router and hence we assign
these to a socket of their own and do not run special-
ized functions at cores in this socket. This avoids having
the IP and rule execution engines contend with special-
ized functions for cache, CPU and other resources at the
cost of some potential inefficiency since these “reserved”
cores (if unused) cannot be used by specialized functions
(if needed). We then assign specialized functions to the
remaining “unreserved” cores. We rely on the existing
(Click and Linux in our implementation) system sched-
ulers to ensure fair sharing of CPU resources between
functions on the same core.
To achieve high performance, we run a single thread
performing both IP forwarding and rule execution at
each of the reserved cores; this ensures that packets that
do not invoke any specialized functions are processed
entirely by a single core avoiding potentially expensive
cache misses and inter-core synchronization [26]. Pack-
ets that invoke specialized functions must be relayed
across cores and hence incur corresponding performance
overheads due to cache misses and so forth. To improve
the efficiency of such transfers when these functions
are implemented in user space, we use shared memory
pages and event queues. In our current prototype, when
a rule invokes a user-level function, we make a single
copy of the packet to the shared memory. An example of
the resulting system architecture is depicted in Fig. 4.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
140
mw Rule Encoding Control mm Signature
120
100
w 80 |
2
co 60 |
=! | Pil hil
o/s aE a a
ES ro & RS CF xo) £ £ 7 > wo Ss <9 & s we”
vs Se we CS < RO a S Ne x ww oe v e
Ss io @ iw? oS
» wey SS e & RS ee & ce & P&S
v 3s Qi PP SF wh 38 oP
N oS NOT ow
Rr YS FP XS
CSP @ SS Sd
Ne »
eS oe
&
Figure 5: Rule Sizes
7 Evaluation
We use our prototype to evaluate the overhead RBF im-
poses on packets (§7.1), routers (§7.2) and RCEs (87.4).
7.1 Packet Size Overhead
Fig. 5 presents rule sizes (in bytes) for a range of exam-
ples, including those from §3.4. The figure captures all
the RBF-related fields and presents the size broken down
into (a) the rule and the associated attributes’ binary
encoding; (b) the control fields used for the lease, RCE
identification, to specify whether the return rule is in the
packet and so forth; and (c) the rule signature. We assume
a 41B signature obtained using ECDSA with ECC public
keys for RCEs derived from the NIST B-163 or K-163
curves [18], offering 80 bits of security. Note that RBF is
independent of the exact signature scheme used and that
smaller (and faster) signatures can be used. However,
shorter RCE keys may require more frequent updates to
compensate for the lower security guarantees. The rules
in Fig. 5 do not contain an identifier, and are identified
by endpoints and routers using a hash over their content.
Rule identifiers are required for rules whose content
may change during a communication (such as the rules
of mobile hosts) and incurs an additional 11B overhead
in our implementation (8B for the hash of the public key
and 3B for the user-selected index). Note that the rule
identifier need be unique only with respect to a single
communication endpoint (i.e., all parties that a host X
communicates with should have unique rule identifiers).
From Fig. 5 we can see that many common forwarding
scenarios (unicast, routing via middleboxes, rules for
DoS protection) can be expressed with around 60-80B
rules while more complex rules (e.g., loose source rout-
ing, secure middleboxes, anycast) can take as much as
140B. The average rule size across all examples we have
implemented is 85B, representing 13% overhead for an
average packet of 630B[4] and 6% overhead for a 1500B
USENIX Association
USENIX Association
packet. By comparison, using RSA-1024 signatures
(instead of ECDSA) would incur 27% overhead on a
630B packet and 11% overhead on a 1500B packet.
Potential Optimization - Rule Caching: Per-packet
overhead can be significantly reduced by caching rules
at endpoints and routers; packets whose rules have been
cached need only carry rule identifiers. There are two op-
portunities for caching. First, destinations can cache re-
turn rules; this allows the return rule to be eliminated
from all but the first packet in a source-to-destination
exchange. Second, rules can also be cached at routers.
Here, however, we must ensure no packet carrying only
a rule identifier arrives at a router that does not store the
corresponding rule description. This might occur, for ex-
ample, due to a route change or when a router deletes
the rule from its cache. In such cases, the router can sim-
ply drop the packet in question, if the endpoints include
the rule on all retransmissions and during periods of high
packet loss. Of course, caching imposes additional stor-
age overhead at routers as we evaluate shortly.
In summary, based on our evaluation, we see that the
per-packet overhead due to RBF can range from as low
as 24B when using caching and up to ~250B in the bad
case where there is no caching and the packet carries
complex destination and return rules.
7.2 Router Overhead
In this section, we evaluate the overhead RBF imposes
on routers for rules that do not invoke specialized
processing functions; we consider router functions in the
following section. The primary overhead RBF imposes
on routers is the additional processing required to
execute and authenticate rules and the additional storage
capacity required if rules are cached. In this paper we
do not evaluate rule authentication, which we assume
is done by specialized hardware at trust-boundary
routers; in [42] we present an evaluation for software
rule authentication using RSA signatures, and show that
our software router is not significantly slowed down
when forwarding realistic traffic traces and performing
verifications (the slowdown is less than 10%).
Rule Forwarding: We first measure the overhead of rule
processing by comparing the performance of RBF-on-
RouteBricks to that of unmodified RouteBricks running
on a single high-end server machine. We use a dual-
socket server with four 2.8GHz Intel Xeon (X5560) cores
per socket to (from) which we generate (sink) traffic over
two dual-port 10G NICs. In this experiment, we use all 8
cores to forward packets.
Fig. 6 plots forwarding rates for some of the examples
from Fig. 5. The first column represents a packet stream
with sizes generated based on a packet trace collected on
the Abilene backbone [11]; since the packets from the
trace do not have rules, we add to each packet the slowest
RouteBricks alone
Gbps
N
Oo
35 m@ RBF over RouteBricks |
30
25
15 F
10 H t
: EEE |
0
Oo ¥
Figure 6: Forwarding speed for RBF over RouteBricks
rule that fits in the packet. By “slowest” we mean the
rule that takes the longest time to forward, as determined
by the number of conditions and actions encountered
during forwarding. To capture the performance impact
for small packets, we profile each rule without any
payload and with no return rules. In the figure, packet
sizes are shown next to the example name and entries
are sorted in order of increasing packet size; the packet
size also includes the Ethernet and IP headers. The last
columns depict forwarding of larger packets, i.e., that
also contain data payload. To see the impact of the
type of rule for these packets, we profiled them with
the fastest and the slowest rules. Note that all rules are
profiled in the worst case, meaning that the longest path
through the rule is considered. For the slowest rule we
use a 145B anycast rule which selects one out of 10
destinations based on the value of a packet attribute.
Overall, we see in Fig. 6 that the performance degrada-
tion due to RBF’s more complex per-packet processing
is always modest (<15%) and virtually non-existent at
larger packet sizes. For small packets the CPU is the for-
warding bottleneck, and RBF’s added processing slows
the router. For larger packets the I/O system is the bot-
tleneck, and there are enough free CPU cycles to execute
rules. A fine-grained profile of the rule execution module
showed that it uses between 120 CPU cycles per packet
for the fastest rule and 600 CPU cycles for the slowest
rule; in comparison, the IP router used in our experi-
ments requires around 3000 cycles per packet without
rule execution. Also note that compared to the network-
level forwarding results from Fig. 6, application-level
goodput is further reduced by the RBF header.
Router cache sizes: We earlier proposed that routers
cache rule authentications and/or rule descriptions. In
each case, the number of cache entries required depends
on the number of distinct rules the router sees. If we
assume that all packets in a flow share the same rule,
then the number of distinct rules passing through a given
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) 389
390
f V_ Firewall onl
© Total in Regular out 4 Encryption tay
© Regularin 4 Snort out 4 Firewall shared
© Snort in © Encryption shared
hennoo4 Gorn Gore Graton 2 20 ” oe
Z P
15 x
0 2 50 75 100 0 5 10 15 20 25
% packets invoking Snort Input Rate Gbps
(a) Isolation of Forwarding (b) Fairness Across Functions
Figure 7: Isolation and Fairness of Router Functions
router varies between the worst case of O(#flows) to the
best case of O(#destinations) seen by the router. The for-
mer corresponds to a destination that uses a different rule
for every source it communicates with, the latter to a des-
tination that uses a single rule for all potential sources.
In our implementation, each cached authentication
is 19 bytes — 11B for the rule identifier and an 8-byte
hash value used to verify whether the rule has changed
since it was authenticated. Each router uses its own
secret hash function to prevent attackers from using
hash collisions. Thus, one million rules would require
only 19MB of memory. For caching entire rules, Fig. 5
reveals average and worst-case rule sizes of 85 and
133 bytes, respectively. If we conservatively assume
traffic is uniformly distributed across these forwarding
categories, we arrive at an estimated cache size of 85MB
(average) to 133MB (worst-case) for 1M rules, which is
within the scope of memory available in current routers.
7.3 Router Functions
Our router prototype supports specialized functions
implemented at either kernel- or user-level. We currently
support three router functions: (i) the Snort IDS [13]
adapted to run as a user-level function, (ii) a kernel-mode
firewall implemented in Click and (iii) a kernel-mode
encryption engine also implemented in Click. Each
function runs as a separate process/kernel thread isolated
from the packet forwarding path through queues. We
measure performance and fairness using the above
functions on the same hardware as before. We dedicate
four cores to the standard forwarding path and the
remaining four cores to custom functions.
Fig. 7(a) illustrates the resource isolation between
forwarding and router functions; the function used in this
experiment is Snort (running on four cores). To generate
traffic we use real traces of (moderately) malicious
traffic created particularly for IDS testing [10, 30]. The
average packet size of the trace used was 1065 bytes. To
avoid biasing our results, we modify Snort not to drop
any malicious packets so packets are only dropped due
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
to resource exhaustion. Our test maintains constant total
input traffic while increasing the percentage of input
traffic that invokes Snort (X-axis). We see from Fig. 7(a)
that Snort traffic does not affect the “regular” traffic that
does not invoke Snort, in the sense that no regular traffic
is dropped, even as a growing percentage of input Snort
traffic is dropped. We observed the same isolation when
using traces with small packets (see [42]).°
Fig. 7(b) illustrates isolation between router functions.
We run three experiments: (1) all traffic invokes the
firewall function and no traffic invokes encryption; (2) all
traffic invokes encryption; and (3) equal halves of traffic
invoking the firewall and encryption. Fig. 7(b) plots
the resulting forwarding rates under increasing input
traffic. In the third (shared) test the CPU is shared fairly
between functions (we use Click-level scheduling); thus,
the ratio between the maximum throughputs achieved
by each router function is expected to roughly match
the ratio between the throughputs of the functions when
running in isolation. In Fig. 7(b) the encryption through-
put is higher for a mix of firewalled and encrypted traffic
than 50% of that when encryption is executed alone
because the trace contains large packets. In this case, the
CPU is not the bottleneck for the firewall functionality
but is the bottleneck for encryption (since it is more
CPU-intensive), and thus encryption ends up using
the leftover firewall CPU cycles. If small packets are
used, both functionalities achieve around 50% of their
throughput in isolation [42]. Note that the high rates
achieved by running each function in isolation illustrates
the benefit of running instances of a single function at
multiple cores (as opposed to one function per core)
since this allows the unused resources from one function
to be seamlessly utilized by other functions.
7.4 RCE Load
We use a simple back-of-the-envelope calculation to
estimate the total number of RCE servers required for the
Internet. The bulk of requests to RCEs are determined
by IP address changes and per-client certifications re-
quested by sites that protect against DoS (by redirecting
DNS requests to powerful entities, see §3.4,84.2). Note
that in the latter case, requests to RCEs are made only
for approved customers. There are currently around 700
million hosts in the Internet [9]; given the current trend
of smart mobile devices we consider | billion hosts. We
assume a worst-case scenario in which all hosts request
certifications in the same second; these requests are
made either by hosts individually to certify a rule or by
°We also measured the performance of the system with all
the eight cores running both forwarding and Snort, and all the
packets directed to Snort. While this configuration does not
provide isolation for the regular traffic, it can forward a higher
total throughput of 22Gbps.
USENIX Association
Flexibility Available to End-hosts Security / Policy Compliance
Property | Explicit Multiple | Invoke Router | Use Router State in] Record Router
Middlebox | Paths Extensions (e.g. | Forwarding (e.g. State (e.g. network
Architecture Support IDS, multicast) | anycast, DTN) probing, ECN)
Mobility | Policy Policy Compliant | Receiver Host Safety of Network
Compliant In-network Reachability | DDoS & Routers (e.g. loops,
Loose Paths _| Functionality Use | Control Protection | break ISP policies)
USENIX Association
RBF Yes Yes Yes Yes Yes
Yes Yes Yes Yes Yes Yes
Active Networks Yes Yes Yes Yes Yes
PushBack, AITF, Stoplt
Predicate routing,
Off-by-default
Yes
Figure 8: A comparison of RBF to:Active Networks [48, 50], ESP[25], i3[47], DOA[49], Platypus[44], SNAPP[40], TVA[52],
SIFF[51], NUTSS[29], PushBack[32], AITF[21], StopIt[37], Predicate Routing[45], Off-by-default[22], ICING[46]
websites hosts are trying to access. We implemented
RCE rule certification in software using RSA signatures,
and measured it on the same 8-core server used through-
out our evaluation. We find a single server can achieve a
certification rate of over 16,000 rules per second. Based
on benchmarks of our implementation and assuming
an oversubscription rate of 10x (ISPs today commonly
oversubscribe by 100 x), the total load due to certifying
rules above could be accommodated by around 6,000
servers; e.g., handled by 20 RCEs with 300 servers each.
Hardware implementations might reduce this number
by more than an order of magnitude. For example,
using recent ECC prototypes [53, 34] a single ASIC
could potentially perform 40,000 RCE certifications per
second, requiring a total of only 2500 such devices.
8 Related Work
RBF is inspired by and extends several directions in past
research. RBF’s contribution is in offering extensive flex-
ibility while respecting policies, where prior approaches
tended to focus on one or the other. Fig. 8 compares the
flexibility and security features of RBF with those of pre-
vious proposals. RBF is complementary to recent efforts
proposing open router APIs [15, 16, 38, 12] — we offer
an overall network design by which endpoints use the
new functionality these router architectures promise to
enable. This paper extends an earlier position paper [43]
that argued the case for a rule-based architecture.
A key feature that distinguishes RBF from previous
proposals and allows it to achieve both flexibility and
policy compliance is its division of functionality between
the data and control planes. Active Networks typically
make little use of the control plane, as they deploy the
forwarding functionality and enforce security on the data
plane. This makes policy compliance hard to achieve. In
contrast, more recent proposals such as OpenFlow [12]
rely heavily on the control plane and install flow state
in the network to make sure the data plane respects the
appropriate policies. This approach, while simplifies the
data plane, results in a more rigid architecture. For ex-
ample, supporting host mobility and traffic engineering
require tearing down the old paths and instantiating new
ones. These are expensive operations which have a nega-
tive impact on the scalability of these proposals. In con-
trast, with RBF, each packet contains (in its rule) enough
information to prove to routers that it respects the poli-
cies of all participants involved in forwarding the packet.
RBF achieves this property despite the fact that neither
the routers nor the packet contain the policies. Thus, RBF
retains the datagram model of the IP, unlike other re-
cent proposals (e.g., network capabilities [52, 51], IC-
ING [46] and OpenFlow [12]), which are more akin
to a connection-oriented model. Finally, while overlay-
based architectures can implement more sophisticated
data plane or control plane mechanisms, they cannot
leverage support at routers and are thus less powerful.’
9 Incremental Deployment
All the benefits of RBF shown in Fig. 8 except re-
ceiver reachability control and DDoS protection can
be achieved with a partial deployment of RBF routers
and middleboxes. In an initial phase, RBF routers could
support both RBF and legacy (non-RBF) traffic. To also
offer DoS protection and reachability control, individual
ASes can upgrade to RBF by dropping legacy traffic.
Hosts in such ASes can use multihoming to handle
legacy traffic, although they will be vulnerable to DoS
attacks on legacy interfaces.
10. Discussion
We have presented RBF, an architecture we have argued
strikes a desirable balance between flexibility and the
ability to guarantee policy compliance of all network
entities. We started this work with two high level goals
in mind. First, we wanted a complete architecture that
supports not only previously proposed communication
primitives, but also future ones. Second, we wanted an
"For example, overlay architectures can only drop un-
wanted packets at overlay nodes and hence cannot create a
network that is fundamentally default-off; once the network-
layer address of a node is known, it can always be attacked at
the underlying network layer.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI’10) 391
392
efficient architecture in which a packet unwanted by a
receiver along its path is dropped as early as possible.
While completeness in this context is difficult to for-
malize, intuitively we have reduced it to (1) supporting
arbitrary communication paths, and (2) allowing all
network entities (i.e., sender, receivers, middleboxes,
and routers) to be involved in the decision process. In
other words, we wanted to be able to define virtually
any forwarding path and give all involved parties a say
in defining it. We noted that such a path can be encoded
by associating with each node an “if-then-else” code
snippet, which specifies the next node down the path.
We further noted that allowing different network entities
to define the communication pattern is equivalent to
allowing them to define these code snippets. This is
roughly what the RBF proposal is.
These goals are ambitious — they subsume, unite and
extend many years of proposals for greater flexibility and
security in networks — and much of RBF’s complexity
follows from these goals.
Finally, one might question whether a relaxation of
RBF’s goals might lead to a significantly simpler design.
This is a valid question that we leave for future work.
We believe that understanding the fundamental tradeoffs
associated with these goals is critical and, at the very
least, that RBF is a step toward arriving at such an
understanding.
Acknowledgments: We would like to thank our shep-
herd, Brad Karp, the anonymous reviewers as well as
Gautam Altekar, Katerina Argyraki, Byung-Gon Chun,
Mihai Dobrescu, Ali Ghodsi, Brighten Godfrey, Gian-
luca Jannaccone, Jayanth Kannan, Eddie Kohler, Pet-
ros Maniatis, Maziar Manesh, Sergiu Nedevschi, Costin
Raiciu, Arsalan Tavakoli and Matei Zaharia for their
feedback on various stages of the paper.
References
[1] AMD Opteron Server Processor. http://www.amd.com/.
[2] Arbor Networks Peakflow: www.arbornetworks.com/en/peakflow-ip-flow-
based-technology. html.
[3] Arista 7100 Series Switches. http://www.aristanetworks.com/en/7100Series.
[4] CAIDA: www.caida.org/data/realtime/.
[5] Certicom Suite B IP Core, http://www.certicom.com.
[6] Cisco Traffic Anomaly Detector: www.cisco.com/en/us/products/ps5892/.
[7] CLP-17: High Performance Elliptic Curve Cryptography (ECC) Point
Multiplier Core, http://www.ellipticsemi.com/products-clp-17.php.
[8] Elliptic Curve Point Multiply and Verify Core, http://www. ipcores.com/
elliptic_curve_crypto-ip_core.htm.
[9] Internet Systems Consortium, Internet Host
https://www.isc.org/solutions/survey/history, 2009.
[10] Lincoln laboratory: Darpa intrusion detection data set (week 6).
http://www.|l.mit.edu/mission/communications/ist/corpora/ideval/data/.
[11] NLANR: Internet Measurement and Analysis. http://moat.nlanr.net.
[12] Open Flow Switch Consortium. http://www.openflowswitch.org.
[13] Snort IDS/IPS, http://www.snort.org.
[14] RFC 1305 - Network Time Protocol. 1992.
[15] Juniper Networks Delivers Platform for Customer and Partner Application
Development. Juniper Press Release, Dec. 2007.
[16] Cisco Opens Routers to Customers and Third-Party Applications. Cisco
Press Release, April 2008.
[17] Next Generation Intel Microarchitecture (Nehalem). http://intel.com, 2008.
[18] Digital Signature Standard (DSS). Federal Information Processing
Standards Publication, June 2009.
Count — History,
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
[19
[20
(34
[35
[36
(37
[39
[40
[41
[42
(44
[45
[46
(47
[48
[49
[50
[51
[52
[53
[54
T. Akin. Hardening Cisco Routers. O’Reilly, 2002.
K. Argyraki and D. R. Cheriton. Loose Source Routing as a Mechanism
for Traffic Policies. In ACM SIGCOMM Workshops, 2004.
K. Argyraki and D. R. Cheriton. Active Internet Traffic Filtering: Real-time
Response to Denialof-Service Attacks. In USENIX Annual Conf., 2005.
H. Ballani, Y. Chawathe, S. Ratnasamy, T. Roscoe, and S. Shenker. Off by
Default! In ACM HotNets, 2005.
R. Beverly and S. Bauer. The Spoofer project: Inferring the extent of
source address filtering on the Internet. In SRUTI Workshop, 2005S.
S. Boyd-Wickizer, R. Morris, and M. F. Kaashoek. Reinventing Scheduling
for Multicore Systems. In HotOS, 2009.
K. L. Calvert, J. Griffioen, and S. Wen. Lightweight Network Support for
Scalable End-to-End Services. In ACM SIGCOMM, August 2002.
M. Dobrescu, N. Egi, K. Argyraki, B.-g. Chun, K. Fall, G. Iannaccone,
A. Knies, M. Manesh, and S. Ratnasamy. RouteBricks: Exploiting
Parallelism to Scale Software Routers. In ACM SOSP, 2009.
N. Egi, A. Greenhalgh, M. Handley, M. Hoerdt, F. Huici, and L. Mathy.
Towards High Performance Virtual Routers on Commodity Hardware. In
Proceedings of 4th Conference on Future Networking Technologies (ACM
CoNEXT 2008), Madrid, Spain, December 2008.
A. Greenhalgh, F. Huici, M. Hoerdt, P. Papadimitriou, M. Handley, and
L. Mathy. Flow processing and the rise of commodity network hardware.
SIGCOMM Comput. Commun. Rev., 39(2), April 2009.
S. Guha and P. Francis. An End-Middle-End Approach to Connection
Establishment. In ACM SIGCOMM, 2007.
J. W. Haines, R. P. Lippmann, D. J. Fried, E. Tran, S. Boswell, and M. A.
Zissman. 1999 DARPA Intrusion Detection System Evaluation: Design
and Procedures. In MIT Lincoln Laboratory Technical Report.
S. Han, K. Jang, K. Park, and S. Moon. PacketShader: A GPU-Accelerated
Software Router. In ACM SIGCOMM, 2010.
J. Ioannidis and S. M. Bellovin. Implementing Pushback: Router-Based
Defense Against DDoS Attacks. In NDDS, 2002.
V. Jacobson, D. K. Smetters, J. D. Thornton, M. F. Plass, N. H. Briggs, and
R. L. Braynard. Networking Named Content. In ACM CoNEXT), 2009.
K. Jarvinen et al. Fast point multiplication on Koblitz curves: Paralleliza-
tion method and implementations. Microprocess. Microsyst., 33(2), 2009.
T. Koponen, M. Chawla, B.-G. Chun, A. Ermolinskiy, K. H. Kim,
S. Shenker, and I. Stoica. A data-oriented (and beyond) network
architecture. In ACM SIGCOMM, 2007.
X. Liu, A. Li, X. Yang, and D. Wetherall. Passport: Secure and Adoptable
Source Authentication. In USENIX NSDI, 2008.
X. Liu, X. Yang, and Y. Lu. To Filter or to Authorize: Network-Layer DoS
Defense Against Multimillion-node Botnets. In ACM SIGCOMM, 2008.
J. C. Mogul, P. Yalagandula, J. Tourrilhes, R. McGeer, S. Banerjee,
T. Connors, and P. Sharma. API Design Challenges for Open Router
Platforms on Proprietary Hardware. In ACM Hotnets, 2008.
R. Morris, E. Kohler, J. Jannotti, and M. F. Kaashoek. The click modular
router. SIGOPS Oper. Syst. Rev., 33(5):217-231, 1999.
B. Parno, A. Perrig, and D. G. Andersen. SNAPP: Stateless Network-
Authenticated Path Pinning. In ACM ASIACCS, 2008.
B. Parno, D. Wendlandt, E. Shi, A. Perrig, B. Maggs, and Y.-C. Hu.
Portcullis: Protecting Connection Setup from Denial-of-Capability
Attacks. In ACM SIGCOMM, 2007.
L. Popa, N. Egi, S. Ratnasamy, and I. Stoica. Rule-based Forwarding
(RBF): Improving the Internet’s Flexibility and Security. UCB Technical
Report, 2010. http://www.eecs.berkeley.edu/%7Epopa/rbfTechReport. pdf.
L. Popa, I. Stoica, and S. Ratnasamy. Rule-based Forwarding (RBF):
improving the Internet’s flexibility and security. In ACM Hotnets, 2009.
B. Raghavan and A. C. Snoeren. A System for Authenticated Policy-
Compliant Routing. In ACM SIGCOMM, 2004.
T. Roscoe, S. Hand, R. Isaacs, R. Mortier, and P. Jardetzky. Predicate
Routing: Enabling Controlled Networking. In ACM Hotnets, 2002.
A. Seehra, J. Nous, M. Walfish, D. Mazieres, A. Nicolosi, and S. Shenker.
A Policy Framework for the Future Internet. In ACM Hotnets, 2009.
I. Stoica, D. Adkins, S. Zhuang, S. Shenker, and S. Surana. Internet
Indirection Infrastructure. In ACM SIGCOMM, 2002.
D. L. Tennenhouse, J. M. Smith, W. D. Sincoskie, D. J. Wetherall, and
G. J. Minden. A Survey of Active Network Research. JEEE Comm., 1997.
M. Walfish, J. Stribling, M. Krohn, H. Balakrishnan, R. Morris, and
S. Shenker. Middleboxes no longer considered harmful. In OSDI, 2004.
D. Wetherall. | Active network vision and reality: Lessons from a
capsulebased system. In ACM SOSP, 1999.
A. Yaar, A. Perrig, and D. Song. SIFF: A Stateless Internet Flow Filter to
Mitigate DDoS Flooding Attacks. In EEE Symp. on Sec. and Priv., 2004.
X. Yang, D. J. Wetherall, and T. Anderson. A DoS-limiting Network
Architecture. In ACM SIGCOMM, 2005.
Y. Zhang, D. Chen, Y. Choi, L. Chen, and S.-B. Ko. A high performance
ECC hardware implementation with instruction-level parallelism over
GF(2163). Microprocess. Microsyst., 34(6), 2010.
S. Zhuravleva, S. Blagodurov, et al. | Addressing Shared Resource
Contention in Multicore Processors via Scheduling. In ASPLOS, 2010.
USENIX Association
USENIX Association
TaintDroid: An Information-Flow Tracking System for Realtime Privacy
Monitoring on Smartphones
William Enck
The Pennsylvania State University
Landon P. Cox
Duke University
Jaeyeon Jung
Intel Labs
Abstract
Today’s smartphone operating systems frequently fail
to provide users with adequate control over and visibility
into how third-party applications use their private data.
We address these shortcomings with TaintDroid, an ef-
ficient, system-wide dynamic taint tracking and analy-
sis system capable of simultaneously tracking multiple
sources of sensitive data. TaintDroid provides realtime
analysis by leveraging Android’s virtualized execution
environment. TaintDroid incurs only 14% performance
overhead on a CPU-bound micro-benchmark and im-
poses negligible overhead on interactive third-party ap-
plications. Using TaintDroid to monitor the behavior of
30 popular third-party Android applications, we found
68 instances of potential misuse of users’ private infor-
mation across 20 applications. Monitoring sensitive data
with TaintDroid provides informed use of third-party ap-
plications for phone users and valuable input for smart-
phone security service firms seeking to identify misbe-
having applications.
1 Introduction
A key feature of modern smartphone platforms is a
centralized service for downloading third-party applica-
tions. The convenience to users and developers of such
“app stores” has made mobile devices more fun and use-
ful, and has led to an explosion of development. Apple’s
App Store alone served nearly 3 billion applications af-
ter only 18 months [4]. Many of these applications com-
bine data from remote cloud services with information
from local sensors such as a GPS receiver, camera, mi-
crophone, and accelerometer. Applications often have le-
gitimate reasons for accessing this privacy sensitive data,
but users would also like assurances that their data is used
properly. Incidents of developers relaying private infor-
mation back to the cloud [35, 12] and the privacy risks
posed by seemingly innocent sensors like accelerome-
ters [19] illustrate the danger.
The Pennsylvania State University
Peter Gilbert Byung-Gon Chun
Duke University Intel Labs
Patrick McDaniel Anmol N. Sheth
Intel Labs
Resolving the tension between the fun and utility of
running third-party mobile applications and the privacy
risks they pose is a critical challenge for smartphone plat-
forms. Mobile-phone operating systems currently pro-
vide only coarse-grained controls for regulating whether
an application can access private information, but pro-
vide little insight into how private information is actu-
ally used. For example, if a user allows an application
to access her location information, she has no way of
knowing if the application will send her location to a
location-based service, to advertisers, to the application
developer, or to any other entity. As a result, users must
blindly trust that applications will properly handle their
private data. This lack of transparency forces users to
blindly trust that applications will properly handle pri-
vate data.
This paper describes TaintDroid, an extension to the
Android mobile-phone platform that tracks the flow of
privacy sensitive data through third-party applications.
TaintDroid assumes that downloaded, third-party appli-
cations are not trusted, and monitors—in realtime—how
these applications access and manipulate users’ personal
data. Our primary goals are to detect when sensitive data
leaves the system via untrusted applications and to facil-
itate analysis of applications by phone users or external
security services [33, 55].
Analysis of applications’ behavior requires sufficient
contextual information about what data leaves a device
and where it is sent. Thus, TaintDroid automatically
labels (taints) data from privacy-sensitive sources and
transitively applies labels as sensitive data propagates
through program variables, files, and interprocess mes-
sages. When tainted data are transmitted over the net-
work, or otherwise leave the system, TaintDroid logs the
data’s labels, the application responsible for transmitting
the data, and the data’s destination. Such realtime feed-
back gives users and security services greater insight into
what mobile applications are doing, and can potentially
identify misbehaving applications.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) = 393
394
To be practical, the performance overhead of the Taint-
Droid runtime must be minimal. Unlike existing so-
lutions that rely on heavy-weight whole-system emula-
tion [7, 57], we leveraged Android’s virtualized archi-
tecture to integrate four granularities of taint propaga-
tion: variable-level, method-level, message-level, and
file-level. Though the individual techniques are not
new, our contributions lie in the integration of these
techniques and in identifying an appropriate trade-off
between performance and accuracy for resource con-
strained smartphones. Experiments with our prototype
for Android show that tracking incurs a runtime over-
head of less than 14% for a CPU-bound microbench-
mark. More importantly, interactive third-party applica-
tions can be monitored with negligible perceived latency.
We evaluated the accuracy of TaintDroid using 30 ran-
domly selected, popular Android applications that use lo-
cation, camera, or microphone data. TaintDroid correctly
flagged 105 instances in which these applications trans-
mitted tainted data; of the 105, we determined that 37
were clearly legitimate. TaintDroid also revealed that 15
of the 30 applications reported users’ locations to remote
advertising servers. Seven applications collected the de-
vice ID and, in some cases, the phone number and the
SIM card serial number. In all, two-thirds of the applica-
tions in our study used sensitive data suspiciously. Our
findings demonstrate that TaintDroid can help expose po-
tential misbehavior by third-party applications.
Like similar information-flow tracking systems [7,
57], a fundamental limitation of TaintDroid is that it can
be circumvented through leaks via implicit flows. The
use of implicit flows to avoid taint detection is, in and of
itself, an indicator of malicious intent, and may well be
detectable through other techniques such as automated
static code analysis [14, 46] as we discuss in Section 8.
The rest of this paper is organized as follows: Sec-
tion 2 provides a high-level overview of TaintDroid, Sec-
tion 3 describes background information on the Android
platform, Section 4 describes our TaintDroid design,
Section 5 describes the taint sources tracked by Taint-
Droid, Section 6 presents results from our Android ap-
plication study, Section 7 characterizes the performance
of our prototype implementation, Section 8 discusses the
limitations of our approach, Section 9 describes related
work, and Section 10 summarizes our conclusions.
2 Approach Overview
We seek to design a framework that allows users to
monitor how third-party smartphone applications handle
their private data in realtime. Many smartphone appli-
cations are closed-source, therefore, static source code
analysis is infeasible. Even if source code is available,
runtime events and configuration often dictate informa-
tion use; realtime monitoring accounts for these environ-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
ment specific dependencies.
Monitoring network disclosure of privacy sensitive in-
formation on smartphones presents several challenges:
e Smartphones are resource constrained. The re-
source limitations of smartphones precludes the use
of heavyweight information tracking systems such
as Panorama [57].
e Third-party applications are entrusted with several
types of privacy sensitive information. The mon-
itoring system must distinguish multiple informa-
tion types, which requires additional computation
and storage.
e Context-based privacy sensitive information is dy-
namic and can be difficult to identify even when
sent in the clear. For example, geographic locations
are pairs of floating point numbers that frequently
change and are hard to predict.
e Applications can share information. Limiting the
monitoring system to a single application does not
account for flows via files and IPC between applica-
tions, including core system applications designed
to disseminate privacy sensitive information.
We use dynamic taint analysis [57, 44, 8, 61, 39] (also
called “taint tracking”) to monitor privacy sensitive in-
formation on smartphones. Sensitive information is first
identified at a taint source, where a taint marking indi-
cating the information type is assigned. Dynamic taint
analysis tracks how labeled data impacts other data in a
way that might leak the original sensitive information.
This tracking is often performed at the instruction level.
Finally, the impacted data is identified before it leaves
the system at a faint sink (usually the network interface).
Existing taint tracking approaches have several lim-
itations. First and foremost, approaches that rely on
instruction-level dynamic taint analysis using whole sys-
tem emulation [57, 7, 26] incur high performance penal-
ties. Instruction-level instrumentation incurs 2-20 times
slowdown [57, 7] in addition to the slowdown introduced
by emulation, which is not suitable for realtime analysis.
Second, developing accurate taint propagation logic has
proven challenging for the x86 instruction set [40, 48].
Implementations of instruction-level tracking can experi-
ence taint explosion if the stack pointer becomes falsely
tainted [49] and taint loss if complicated instructions
such as CMPXCHG, REP MOV are not instrumented
properly [61]. While most smartphones use the ARM
instruction set, similar false positives and false negatives
could arise.
Figure 1 presents our approach to taint tracking on
smartphones. We leverage architectural features of vir-
tual machine-based smartphones (e.g., Android, Black-
Berry, and J2ME-based phones) to enable efficient,
USENIX Association
USENIX Association
Message-level tracking
v
Application Code Application Code
Virtual Virtual lg. Variable-level
Machine Machine tracking
: ’ ‘ Method-level
Native System Libraries I+ trackin g
File-level
Network Interface Secondary Storage I+ trackin 9
Figure 1: Multi-level approach for performance efficient
taint tracking within a common smartphone architecture.
system-wide taint tracking using fine-grained labels with
clear semantics. First, we instrument the VM interpreter
to provide variable-level tracking within untrusted ap-
plication code.! Using variable semantics provided by
the interpreter provides valuable context for avoiding
the taint explosion observed in the x86 instruction set.
Additionally, by tracking variables, we maintain taint
markings only for data and not code. Second, we use
message-level tracking between applications. Tracking
taint on messages instead of data within messages mini-
mizes IPC overhead while extending the analysis system-
wide. Third, for system-provided native libraries, we use
method-level tracking. Here, we run native code with-
out instrumentation and patch the taint propagation on
return. These methods accompany the system and have
known information flow semantics. Finally, we use file-
level tracking to ensure persistent information conserva-
tively retains its taint markings.
To assign labels, we take advantage of the well-
defined interfaces through which applications access sen-
sitive data. For example, all information retrieved from
GPS hardware is location-sensitive, and all informa-
tion retrieved from an address book database is contact-
sensitive. This avoids relying on heuristics [10] or man-
ual specification [61] for labels. We expand on informa-
tion sources in Section 5.
In order to achieve this tracking at multiple granulari-
ties, our approach relies on the firmware’s integrity. The
taint tracking system’s trusted computing base includes
the virtual machine executing in userspace and any na-
tive system libraries loaded by the untrusted interpreted
application. However, this code is part of the firmware,
and is therefore trusted. Applications can only escape
the virtual machine by executing native methods. In our
target platform (Android), we modified the native library
loader to ensure that applications can only load native li-
braries from the firmware and not those downloaded by
the application. Note that an early 2010 survey of the top
50 most popular free applications in each category of the
Android Market [2] (1100 applications in total) revealed
that less than 4% included a . so file. A similar survey
conducted in mid 2010 revealed this fraction increased to
5%, which indicates there is growth in the number of ap-
plications using native third-party libraries, but that the
number of affected applications remains small.
In summary, we provide a novel, efficient, system-
wide, multiple-marking, taint tracking design by com-
bining multiple granularities of information tracking.
While some techniques such as variable tracking within
an interpreter have been previously proposed (see Sec-
tion 9), to our knowledge, our approach is the first to
extend such tracking system-wide. By choosing a mul-
tiple granularity approach, we balance performance and
accuracy. As we show in Sections 6 and 7, our system-
wide approach is both highly efficient (~ 14% CPU over-
head and ~4.4% memory overhead for simultaneously
tracking 32 taint markings per data unit) and accurately
detects many suspicious network packets.
3 Background: Android
Android [1] is a Linux-based, open source, mobile
phone platform. Most core phone functionality is imple-
mented as applications running on top of a customized
middleware. The middleware itself is written in Java
and C/C++. Applications are written in Java and com-
piled to a custom byte-code known as the Dalvik EXe-
cutable (DEX) byte-code format. Each application exe-
cutes within its Dalvik VM interpreter instance. Each in-
stance executes as unique UNIX user identities to isolate
applications within the Linux platform subsystem. Ap-
plications communicate via the binder IPC mechanism.
Binder provides transparent message passing based on
parcels. We now discuss topics necessary to understand
our tracking system.
Dalvik VM Interpreter: DEX is a register-based ma-
chine language, as opposed to Java byte-code, which is
stack-based. Each DEX method has its own predefined
number of virtual registers (which we frequently refer to
as simply “registers”). The Dalvik VM interpreter man-
ages method registers with an internal execution state
stack; the current method’s registers are always on the
top stack frame. These registers loosely correspond to
local variables in the Java method and store primitive
types and object references. All computation occurs
on registers, therefore values must be loaded from and
stored to class fields before use and after use. Note that
DEX uses class fields for all long term storage, unlike
hardware register-based machine languages (e.g., x86),
which store values in arbitrary memory locations.
Native Methods: The Android middleware provides ac-
cess to native libraries for performance optimization and
third-party libraries such as OpenGL and Webkit. An-
droid also uses Apache Harmony Java [3], which fre-
quently uses system libraries (e.g., math routines). Na-
tive methods are written in C/C++ and expose function-
ality provided by the underlying Linux kernel and ser-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) 395
396
3 Trusted Application Untrusted Application
°
ZU
3 (8)
&] | {| TaintSource (1) 0 fF
z meu enuSoe,
(9)
oO
Ef
g Dalvik VM Dalvik VM
S | {Interpreter Interpreter
| Binder Hook | Binder IPC Library
£ { Binder Kernel Module
ot
Figure 2: TaintDroid architecture within Android.
vices. They can also access Java internals, and hence are
included in our trusted computing base (see Section 2).
Android contains two types of native methods: inter-
nal VM methods and JNI methods. The internal VM
methods access interpreter-specific structures and APIs.
JNI methods conform to Java native interface standards
specifications [32], which requires Dalvik to separate
Java arguments into variables using a JNI call bridge.
Conversely, internal VM methods must manually parse
arguments from the interpreter’s byte array of arguments.
Binder IPC: All Android IPC occurs through binder.
Binder is a component-based processing and IPC frame-
work designed for BeOS, extended by Palm Inc., and
customized for Android by Google. Fundamental to
binder are parcels, which serialize both active and stan-
dard data objects. The former includes references to
binder objects, which allows the framework to manage
shared data objects between processes. A binder kernel
module passes parcel messages between processes.
4 TaintDroid
TaintDroid is a realization of our multiple granularity
taint tracking approach within Android. TaintDroid uses
variable-level tracking within the VM interpreter. Mul-
tiple taint markings are stored as one taint tag. When
applications execute native methods, variable taint tags
are patched on return. Finally, taint tags are assigned
to parcels and propagated through binder. Note that
the Technical Report [17] version of this paper contains
more implementation details.
Figure 2 depicts TaintDroid’s architecture. Informa-
tion is tainted (1) in a trusted application with sufficient
context (e.g., the location provider). The taint inter-
face invokes a native method (2) that interfaces with the
Dalvik VM interpreter, storing specified taint markings
in the virtual taint map. The Dalvik VM propagates taint
tags (3) according to data flow rules as the trusted ap-
plication uses the tainted information. Every interpreter
instance simultaneously propagates taint tags. When the
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
trusted application uses the tainted information in an IPC
transaction, the modified binder library (4) ensures the
parcel has a taint tag reflecting the combined taint mark-
ings of all contained data. The parcel is passed transpar-
ently through the kernel (5) and received by the remote
untrusted application. Note that only the interpreted code
is untrusted. The modified binder library retrieves the
taint tag from the parcel and assigns it to all values read
from it (6). The remote Dalvik VM instance propagates
taint tags (7) identically for the untrusted application.
When the untrusted application invokes a library spec-
ified as a taint sink (8), e.g., network send, the library
retrieves the taint tag for the data in question (9) and re-
ports the event.
Implementing this architecture requires addressing
several system challenges, including: a) taint tag stor-
age, b) interpreted code taint propagation, c) native code
taint propagation, d) IPC taint propagation, and e) sec-
ondary storage taint propagation. The remainder of this
section describes our design.
4.1 Taint Tag Storage
The choice of how to store taint tags influences per-
formance and memory overhead. Dynamic taint track-
ing systems commonly store tags for every data byte or
word [57, 7]. Tracked memory is unstructured and with-
out content semantics. Frequently taint tags are stored
in non-adjacent shadow memory [57] and tag maps [61].
TaintDroid uses variable semantics within the Dalvik in-
terpreter. We store taint tags adjacent to variables in
memory, providing spatial locality.
Dalvik has five variable types that require taint stor-
age: method local variables, method arguments, class
static fields, class instance fields, and arrays. In all cases,
we store a 32-bit bitvector with each variable to encode
the taint tag, allowing 32 different taint markings.
Dalvik stores method local variables and arguments
on an internal stack. When an application invokes a
method, a new stack frame is allocated for all local vari-
ables. Method arguments are also passed via the internal
stack. Before calling a method, the callee places the ar-
guments on the top of the stack such that they become
high numbered registers in the callee’s stack frame. We
allocate taint tag storage by doubling the size of the stack
frame allocation. Taint tags are interleaved between val-
ues such that register v; originally accessed via fp|i] is
accessed as fp[2 - i] after modification. Note that Dalvik
stores 64-bit variables as two adjacent 32-bit registers on
the internal stack. While the byte-code interprets these
adjacent registers as a single 64-bit value, the interpreter
manages these registers as separate values. Therefore,
our modified stack transparently stores and retrieves 64-
bit values to and from separate 32-bit registers (at fp[2-7]
and fp|2- i+ 2]). Finally, native method targets require
USENIX Association
USENIX Association
Low Addresses (0x00000000) Interpreted Targets Native Targets
out®
stack pointer (top)
VM goop
frame pointer (current) SORERISEETG
v@ taint tag
out® vi == ind arg®
out® taint tag v1 taint tag arg1
out1 v2 == inl return taint
out1 taint tag v2 taint tag arg®@ taint tag
Cunused) arg1 taint tag
VM goop
frame pointer (previous)
v@ == local }—— variable
v@ taint tag }<«—— variable taint tag
vi == locall
v1 taint tag
v2 == ind
v4 taint tag
High Addresses (Oxf ff ff fff)
Figure 3: Modified Stack Format. Taint tags are inter-
leaved between registers for interpreted method targets
and appended for native methods. Dark grayed boxes
represent taint tags.
a slightly different stack frame organization for reasons
discussed in Section 4.3. The modified stack format is
shown in Figure 3.
Taint tags are stored adjacent to class fields and ar-
rays inside the VM interpreter’s internal data structures.
TaintDroid stores only one taint tag per array to minimize
storage overhead. Per-value taint tag storage is severely
inefficient for Java String objects, as all characters have
the same tag. Unfortunately, storing one taint tag per ar-
ray may result in false positives during taint propagation.
For example, if untainted variable wu is stored into array A
at index 0 (A[0]) and tainted variable t is stored into A[1],
then array A is tainted. Later, if variable v is assigned
to A[0], v will be tainted, even though u was untainted.
Fortunately, Java frequently uses objects, and object ref-
erences are infrequently tainted (see Section 4.2), there-
fore this coding practice leads to less false positives.
4.2 Interpreted Code Taint Propagation
Taint tracking granularity and flow semantics influ-
ence performance and accuracy. TaintDroid implements
variable-level taint tracking within the Dalvik VM in-
terpreter. Variables provide valuable semantics for taint
propagation, distinguishing data pointers from scalar val-
ues. TaintDroid primarily tracks primitive type variables
(e.g., int, float, etc); however, there are cases when object
references must become tainted to ensure taint propaga-
tion operates correctly; this section addresses why these
cases exist. However, first we present taint tracking in
the Dalvik machine language as a formal logic.
4.2.1 Taint Propagation Logic
The Dalvik VM operates on the unique DEX machine
language instruction set, therefore we must design an ap-
propriate propagation logic. We use a data flow logic, as
tracking implicit flows requires static analysis and causes
significant performance overhead and overestimation in
tracking [29] (see Section 8). We begin by defining taint
markings, taint tags, variables, and taint propagation. We
then present our logic rules for DEX.
Let £ be the universe of taint markings for a particular
system. A taint tag ¢ is a set of taint markings, t C CL.
Each variable has an associated taint tag. A variable is an
instance of one of the five types described in Section 4.1.
We use a different representation for each type. The local
and argument variables correspond to virtual registers,
denoted v,,. Class field variables are denoted as f, to in-
dicate a field variable with class index x. Instance fields
require an instance object and are denoted v,( fz), where
Uy is the instance object reference (note that both the ob-
ject reference and the dereferenced value are variables).
Static fields are denoted as f, alone, which is shorthand
for S(f), where S'() is the static scope. Finally, v,[-]
denotes an array, where vz is an array object reference
variable.
Our virtual taint map function is 7(-). 7(v) returns the
taint tag t for variable v. 7(v) is also used to assign a
taint tag to a variable. Retrieval and assignment are dis-
tinguished by the position of 7(-) w.rt. the — symbol.
When 7(v) appears on the right hand side of —, 7(v) re-
trieves the taint tag for v. When 7(v) appears on the left
hand side, 7(v) assigns the taint tag for v. For example,
T(v1) — T(v2) copies the taint tag from v2 to v4.
Table 1 captures our propagation logic. The table enu-
merates abstracted versions of the byte-code instructions
specified in the DEX documentation. Register variables
and class fields are referenced by vx and fx, respec-
tively. R and F are the return and exception variables
maintained within the interpreter, respectively. A, B, and
C are constants in the byte-code. The table does not list
instructions that clear the taint tag of the destination reg-
ister. For example, we do not consider the array-length
instruction to return a tainted value even if the array is
tainted. Note that the array length is sometimes used to
aid direct control flow propagation (e.g., Vogt et al. [53]).
4.2.2 Tainting Object References
The propagation rules in Table | are straightforward
with two exceptions. First, taint propagation logics com-
monly include the taint tag of an array index during
lookup to handle translation tables (e.g., ASCII/UNI-
CODE or character case conversion). For example, con-
sider a translation table from lowercase to upper case
characters: if a tainted value “a” is used as an array index,
the resulting “A” value should be tainted even though the
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) 397
398
Table 1: DEX Taint Propagation Logic. Register variables and class fields are referenced by vx and fx, respectively.
Rand EF are the return and exception variables maintained within the interpreter. A, B, and C are byte-code constants.
Op Format Op Semantics Taint Propagation Description
const-op va C va C T(va) —O Clear v , taint
move-op VA UB VA <— UB T(vAa) — T(vB) Set v, taint to vg taint
move-op-R vA va~R T(va) — T(R) Set uv taint to return taint
return-op VA Ro-va 7T(R) — T(va) Set return taint (@ if void)
move-op-E vA vaAcE T(va) — T(B) Set v4 taint to exception taint
throw-op VA Ev, T(E) —T(va) Set exception taint
unary-op VA UB VA — @uB T(vA) — T(vB) Set v4 taint to vg taint
binary-op VA VB VG VA —VBRVUG T(vA) —T(UB)UT(UC) Set vg taint to vg taint U vo taint
binary-op VA UB VA—vUA®BVUB TVA) —T(VA)UT(vB) Update v4 taint with vp taint
binary-op vA vB C VA —VBeC T(vA) — T(vB) Set v, taint to vg taint
aput-op UA UB UG vplvc] — vA T(vB[]) —T(vB[]) UT(va) — Update array vg taint with vy taint
aget-op VA UB VC vA — uBluc] T(va) — T(vBl]) U Tuc) Set va taint to array and index taint
sput-op vA fB fava t(fB) — Twa) Set field fg taint to vy taint
sget-op vA fB vA —fB T(va) — T(fB) Set va taint to field fg taint
iput-op vA UB fo vB(fc) — VA T(uB(fo)) — T(va) Set field fo taint to vy taint
iget-op v4 UB fo va — uB(fo) T(va) — T(vB(fo))UT(vB) Set vg taint to field f¢ and object reference taint
public static Integer valueOf (int
LE -(1 < =128: || ode S127)" 4
return new Integer(i); }
return valueOfCache.CACHE [i+128];
}
static class valueOfCache {
static final Integer[] CACHE = new Integer[256];
static {
for(int i=-128; i<=127; i++) {
CACHE[i+128] = new Integer(i); } }
}
Figure 4: Excerpt from Android’s Integer class illustrat-
ing the need for object reference taint propagation.
“A” value in the array is not. Hence, the taint logic for
aget-op uses both the array and array index taint. Sec-
ond, when the array contains object references (e.g., an
Integer array), the index taint tag is propagated to the ob-
ject reference and not the object value. Therefore, we
include the object reference taint tag in the instance get
(iget-op) rule.
The code listed in Figure 4 demonstrates a real in-
stance of where object reference tainting is needed. Here,
valueOf() returns an Integer object for a passed int. If the
int argument is between —128 and 127, valueOf() returns
reference to a statically defined Integer object. valueOf()
is implicitly called for conversion to an object. Consider
the following definition and use of a method intProxy().
Object intProxy(int val) { return val; }
int out (Integer) intProxy(tVal);
Consider the case where tVal is an int with value 1
and taint tag TAG. When intProxy() is passed tVal, TAG
is propagated to val. When intProxy() returns val, it
calls Integer.valueOf() to obtain an Integer instance cor-
responding to the scalar variable val. In this case, Inte-
ger.valueOf() returns a reference to the static Integer ob-
ject with value 1. The value field (of the Integer class) in
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
the object has taint tag of §; however, since the aget-op
propagation rule includes the taint of the index register,
the object reference has a taint tag of TAG. Therefore,
only by including the object reference taint tag when the
value field is read from the Integer (i.e., the iget-op prop-
agation rule), will the correct taint tag of TAG be assigned
to out.
4.3 Native Code Taint Propagation
Native code is unmonitored in TaintDroid. Ideally,
we achieve the same propagation semantics as the in-
terpreted counterpart. Hence, we define two necessary
postconditions for accurate taint tracking in the Java-
like environment: 1) all accessed external variables (i.e.,
class fields referenced by other methods) are assigned
taint tags according to data flow rules; and 2) the re-
turn value is assigned a taint tag according to data flow
tules. TaintDroid achieves these postconditions through
an assortment of manual instrumentation, heuristics, and
method profiles, depending on situational requirements.
Internal VM Methods: Internal VM methods are called
directly by interpreted code, passing a pointer to an ar-
ray of 32-bit register arguments and a pointer to a return
value. The stack augmentation shown in Figure 3 pro-
vides access to taint tags for both Java arguments and
the return value. As there are a relatively small number
of internal VM methods which are infrequently added
between versions,” we manually inspected and patched
them for taint propagation as needed. We identified 185
internal VM methods in Android version 2.1; however,
only 5 required patching: the System.arraycopy() native
method for copying array contents, and several native
methods implementing Java reflection.
JNI Methods: JNI methods are invoked through the
JNI call bridge. The call bridge parses Java arguments
and assigns a return value using the method’s descriptor
USENIX Association
USENIX Association
string. We patched the call bridge to provide taint propa-
gation for all JNI methods. When a JNI method returns,
TaintDroid consults a method profile table for tag propa-
gation updates. A method profile is a list of (from, to)
pairs indicating flows between variables, which may be
method parameters, class variables, or return values.
Enumerating the information flows for all JNI methods
is a time consuming task best completed automatically
using source code analysis (a task we leave for future
work). We currently include an additional propagation
heuristic patch. The heuristic is conservative for JNI
methods that only operate on primitive and String ar-
guments and return values. It assigns the union of the
method argument taint tags to the taint tag of the return
value. While the heuristic has false negatives for meth-
ods using objects, it covers many existing methods.
We performed a survey of the JNI methods included
in the official Android source code (version 2.1) to de-
termine specific properties. We found 2,844 JNI meth-
ods with a Java interface and C or C++ implementation.?
Of these methods, 913 did not reference objects (as argu-
ments, return value, or method body) and hence are auto-
matically covered by our heuristic. The remaining meth-
ods may or may not have information flows that produce
false negatives. Currently, we define method profiles as
needed. For example, methods in the IBM NativeCon-
verter class require propagation for conversion between
character and byte arrays.
4.4 IPC Taint Propagation
Taint tags must propagate between applications when
they exchange data. The tracking granularity affects
performance and memory overhead. TaintDroid uses
message-level taint tracking. A message taint tag repre-
sents the upper bound of taint markings assigned to vari-
ables contained in the message. We use message-level
granularity to minimize performance and storage over-
head during IPC.
We chose to implement message-level over variable-
level taint propagation, because in a variable-level sys-
tem, a devious receiver could game the monitoring by
unpacking variables in a different way to acquire val-
ues without taint propagation. For example, if an IPC
parcel message contains a sequence of scalar values, the
receiver may unpack a string instead, thereby acquiring
values without propagating all the taint tags on scalar val-
ues in the sequence. Hence, to prevent applications from
removing taint tags in this way, the current implementa-
tion protects taint tags at the message-level.
Message-level taint propagation for IPC leads to false
positives. Similar to arrays, all data items in a parcel
share the same taint tag. For example, Section 8 dis-
cusses limitations for tracking the IMSI that results from
passing as portions the value as configuration parameters
in parcels. Future implementations will consider word-
level taint tags along with additional consistency checks
to ensure accurate propagation for unpacked variables.
However, this additional complexity will negatively im-
pact IPC performance.
4.5 Secondary Storage Taint Propagation
Taint tags may be lost when data is written to a file.
Our design stores one taint tag per file. The taint tag
is updated on file write and propagated to data on file
read. TaintDroid stores file taint tags in the file sys-
tem’s extended attributes. To do this, we implemented
extended attribute support for Android’s host file system
(YAFFS2) and formatted the removable SDcard with the
ext2 file system. As with arrays and IPC, storing one
taint tag per file leads to false positives and limits the
granularity of taint markings for information databases
(see Section 5). Alternatively, we could track taint tags
at a finer granularity at the expense of added memory and
performance overhead.
4.6 Taint Interface Library
Taint sources and sinks defined within the virtualized
environment must communicate taint tags with the track-
ing system. We abstract the taint source and sink logic
into a single taint interface library. The interface per-
forms two functions: 1) add taint markings to variables;
and 2) retrieve taint markings from variables. The library
only provides the ability to add and not set or clear taint
tags, as such functionality could be used by untrusted
Java code to remove taint markings.
Adding taint tags to arrays and strings via internal VM
methods is straightforward, as both are stored in data ob-
jects. Primitive type variables, on the other hand, are
stored on the interpreter’s internal stack and disappear
after a method is called. Therefore, the taint library uses
the method return value as a means of tainting primitive
type variables. The developer passes a value or variable
into the appropriate add taint method (e.g., addTaintInt())
and the returned variable has the same value but addition-
ally has the specified taint tag. Note that the stack storage
does not pose complications for taint tag retrieval.
5 Privacy Hook Placement
Using TaintDroid for privacy analysis requires iden-
tifying privacy sensitive sources and instrumenting taint
sources within the operating system. Historically, dy-
namic taint analysis systems assume taint source and sink
placement is trivial. However, complex operating sys-
tems such as Android provide applications information
in a variety of ways, e.g., direct access, and service inter-
face. Each potential type of privacy sensitive information
must be studied carefully to determine the best method of
defining the taint source.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) 399
400
Taint sources can only add taint tags to memory for
which TaintDroid provides tag storage. Currently, taint
source and sink placement is limited to variables in in-
terpreted code, IPC messages, and files. This section
discusses how valuable taint sources and sinks can be im-
plemented within these restrictions. We generalize such
taint sources based on information characteristics.
Low-bandwidth Sensors: A variety of privacy sensitive
information types are acquired through low-bandwidth
sensors, e.g., location and accelerometer. Such informa-
tion often changes frequently and is simultaneously used
by multiple applications. Therefore, it is common for
a smartphone OS to multiplex access to low-bandwidth
sensors using a manager. This sensor manager represents
an ideal point for taint source hook placement. For our
analysis, we placed hooks in Android’s LocationMan-
ager and SensorManager applications.
High-bandwidth Sensors: Privacy sensitive informa-
tion sources such as the microphone and camera are
high-bandwidth. Each request from the sensor frequently
returns a large amount of data that is only used by one
application. Therefore, the smartphone OS may share
sensor information via large data buffers, files, or both.
When sensor information is shared via files, the file must
be tainted with the appropriate tag. Due to flexible APIs,
we placed hooks for both data buffer and file tainting for
tracking microphone and camera information.
Information Databases: Shared information such as ad-
dress books and SMS messages are often stored in file-
based databases. This organization provides a useful un-
ambiguous taint source similar to hardware sensors. By
adding a taint tag to such database files, all informa-
tion read from the file will be automatically tainted. We
used this technique for tracking address book informa-
tion. Note that while TaintDroid’s file-level granularity
was appropriate for these valuable information sources,
others may exist for which files are too coarse grained.
However, we have not yet encountered such sources.
Device Identifiers: Information that uniquely identifies
the phone or the user is privacy sensitive. Not all per-
sonally identifiable information can be easily tainted.
However, the phone contains several easily tainted iden-
tifiers: the phone number, SIM card identifiers (IMSI,
ICC-ID), and device identifier (IMEI) are all accessed
through well-defined APIs. We instrumented the APIs
for the phone number, ICC-ID, and IMEI. An IMSI taint
source has inherent limitations discussed in Section 8.
Network Taint Sink: Our privacy analysis identifies
when tainted information transmits out the network in-
terface. The VM interpreter-based approach requires the
taint sink to be placed within interpreted code. Hence,
we instrumented the Java framework libraries at the point
the native socket library is invoked.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
6 Application Study
This section reports on an application study that uses
TaintDroid to analyze how 30 popular third-party An-
droid applications use privacy sensitive user data. Exist-
ing applications acquire a variety of user data along with
permissions to access the Internet. Our study finds that
two thirds of these applications expose detailed location
data, the phone’s unique ID, and the phone number using
the combination of the seemingly innocuous access per-
missions granted at install. This finding was made possi-
ble by TaintDroid’s ability to monitor runtime access of
sensitive user data and to precisely relate the monitored
accesses with the data exposure by applications.
6.1 Experimental Setup
An early 2010 survey of the 50 most popular free ap-
plications in each category of the Android Market [2]
(1,100 applications, in total) revealed that roughly a third
of the applications (358 of the 1,100 applications) re-
quire Internet permissions along with permissions to ac-
cess either location, camera, or audio data. From this set,
we randomly selected 30 popular applications (an 8.4%
sample size), which span twelve categories. Table 2 enu-
merates these applications along with permissions they
request at install time. Note that this does not reflect ac-
tual access or use of sensitive data.
We studied each of the thirty downloaded applica-
tions by starting the application, performing any initial-
ization or registration that was required, and then man-
ually exercising the functionality offered by the appli-
cation. We recorded system logs including detailed in-
formation from TaintDroid: tainted binder messages,
tainted file output, and tainted network messages with
the remote address. The overall experiment (conducted
in May 2010) lasted slightly over 100 minutes, generat-
ing 22,594 packets (8.6MB) and 1,130 TCP connections.
To verify our results, we also logged the network traffic
using tcpdump on the WiFi interface and repeated exper-
iments on multiple Nexus One phones, running the same
version of TaintDroid built on Android 2.1. Though the
phones used for experiments had a valid SIM card in-
stalled, the SIM card was inactivate, forcing all the pack-
ets to be transmitted via the WiFi interface. The packet
trace was used only to verify the exposure of tainted data
flagged by TaintDroid.
In addition to the network trace, we also noted whether
applications acquired user consent (either explicit or im-
plicit) for exporting sensitive information. This provides
additional context information to identify possible pri-
vacy violations. For example, by selecting the “use my
location” option in a weather application, the user im-
plicitly consents to disclosing geographic coordinates to
the weather server.
USENIX Association
Table 2: Applications grouped by the requested permissions (L: location, C: camera, A: audio, P: phone state). Android
Market categories are indicated in parenthesis, showing the diversity of the studied applications.
fy Peemicians’
Applications # ermissions
L Cc A P
The Weather Channel (News & Weather); Cestos, Solitaire (Game); Movies (Entertainment); | 6 x
Babble (Social); Manga Browser (Comics)
Bump, Wertago (Social); Antivirus (Communication); ABC — Animals, Traffic Jam, Hearts, | 14 x x
Blackjack, (Games); Horoscope (Lifestyle); 3001 Wisdom Quotes Lite, Yellow Pages (Ref-
erence); Dastelefonbuch, Astrid (Productivity), BBC News Live Stream (News & Weather);
Ringtones (Entertainment)
Layer (Productivity); Knocking (Social); Barcode Scanner, Coupons (Shopping); Trapster | 7 x x x
USENIX Association
(Travel); Spongebob Slide (Game); ProBasketBall (Sports)
MySpace (Social); ixMAT (Shopping)
Evernote (Productivity)
* All listed applications also require access to the Internet.
Table 3: Potential privacy violations by 20 of the studied applications. Note that three applications had multiple
violations, one of which had a violation in all three categories.
Observed Behavior (# of apps) Details
Phone Information to Content Servers (2)
2 apps sent out the phone number, IMSI, and ICC-ID along with the
geo-coordinates to the app’s content server.
Device ID to Content Servers (7)*
2 Social, 1 Shopping, 1 Reference and three other apps transmitted
the IMEI number to the app’s content server.
Location to Advertisement Servers (15)
5 apps sent geo-coordinates to ad.qwapi.com, 5 apps to admob.com,
2 apps to ads.mobclix.com (1 sent location both to admob.com and
ads.mobclix.com) and 4 apps sent location’ to data.flurry.com.
* TaintDroid flagged nine applications in this category, but only seven transmitted the raw IMEI without mentioning such practice in the EULA.
+ To the best of our knowledge, the binary messages contained tainted location data (see the discussion below).
6.2 Findings
Table 3 summarizes our findings. TaintDroid flagged
105 TCP connections as containing tainted privacy sen-
sitive information. We manually labeled each mes-
sage based on available context, including remote server
names and temporally relevant application log messages.
We used remote hostnames as an indication of whether
data was being sent to a server providing application
functionality or to a third party. Frequently, messages
contained plaintext that aided categorization, e.g., an
HTTP GET request containing geographic coordinates.
However, 21 flagged messages contained binary data.
Our investigation indicates these messages were gen-
erated by the Google Maps for Mobile [21] and Flur-
ryAgent [20] APIs and contained tainted privacy sensi-
tive data. These conclusions are supported by message
transmissions immediately after the application received
a tainted parcel from the system location manager. We
now expand on our findings for each category and reflect
on potential privacy violations.
Phone Information: Table 2 shows that 21 out of the
30 applications require permissions to read phone state
and the Internet. We found that 2 of the 21 applications
transmitted to their server (1) the device’s phone num-
ber, (2) the IMSI which is a unique 15-digit code used to
identify an individual user on a GSM network, and (3)
the ICC-ID number which is a unique SIM card serial
number. We verified messages were flagged correctly by
inspecting the plaintext payload.* In neither case was the
user informed that this information was transmitted off
the phone.
This finding demonstrates that Android’s coarse-
grained access control provides insufficient protection
against third-party applications seeking to collect sensi-
tive data. Moreover, we found that one application trans-
mits the phone information every time the phone boots.
While this application displays a terms of use on first use,
the terms of use does not specify collection of this highly
sensitive data. Surprisingly, this application transmits the
phone data immediately after install, before first use.
Device Unique ID: The device’s IMEI was also exposed
by applications. The IMEI uniquely identifies a specific
mobile phone and is used to prevent a stolen handset
from accessing the cellular network. TaintDroid flags
indicated that nine applications transmitted the IMEI.
Seven out of the nine applications either do not present
an End User License Agreement (EULA) or do not spec-
ify IMEI collection in the EULA. One of the seven ap-
plications is a popular social networking application and
another is a location-based search application. Further-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI’10) 401
402
more, we found two of the seven applications include the
IMEI when transmitting the device’s geographic coordi-
nates to their content server, potentially repurposing the
IMEI as a client ID.
In comparison, two of the nine applications treat the
IMEI with more care, thus we do not classify them as
potential privacy violators. One application displays a
privacy statement that clearly indicates that the applica-
tion collects the device ID. The other uses the hash of
the IMEI instead of the number itself. We verified this
practice by comparing results from two different phones.
Location Data to Advertisement Servers: Half of the
studied applications exposed location data to third-party
advertisement servers without requiring implicit or ex-
plicit user consent. Of the fifteen applications, only two
presented a EULA on first run; however neither EULA
indicated this practice. Exposure of location informa-
tion occurred both in plaintext and in binary format.
The latter highlights TaintDroid’s advantages over sim-
ple pattern-based packet scanning. Applications sent lo-
cation data in plaintext to admob.com, ad.qwapi.com,
ads.mobclix.com (11 applications) and in binary format
to FlurryAgent (4 applications). The plaintext location
exposure to AdMob occurred in the HTTP GET string:
...&S=al4a4a93fle4c68é&. .&t=062A1CB1D476DE85
B717D9195A6722A9&d%5Bcoord%5D=47. 6612278900
00006%2C-122.31589477&...
Investigating the AdMob SDK revealed the s= parameter
is an identifier unique to an application publisher, and the
coord= parameter provides the geographic coordinates.
For FlurryAgent, we confirmed location exposure by
the following sequence of events. First, a component
named “FlurryAgent” registers with the location man-
ager to receive location updates. Then, TaintDroid log
messages show the application receiving a tainted par-
cel from the location manager. Finally, the application
reports “sending report to http://data.flurry.
com/aar.do” after receiving the tainted parcel.
Our experimentation indicates these fifteen applica-
tions collect location data and send it to advertisement
servers. In some cases, location data was transmitted
to advertisement servers even when no advertisement
was displayed in the application. However, we note that
TaintDroid helped us verify that three of the studied ap-
plications (not included in the Table 3) only transmitted
location data per user’s request to pull localized content
from their servers. This finding demonstrates the impor-
tance of monitoring exercised functionality of an appli-
cation that reflects how the application actually uses or
abuses the granted permissions.
Legitimate Flags: Out of 105 connections flagged by
TaintDroid, 37 were deemed clearly legitimate use. The
flags resulted from four applications and the OS itself
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
while using the Google Maps for Mobile (GMM) API.
The TaintDroid logs indicate an HTTP request with the
“User-Agent: GMM ...” header, but a binary pay-
load. Given that GMM functionality includes download-
ing maps based on geographic coordinates, it is obvious
that TaintDroid correctly identified location information
in the payload. Our manual inspection of each message
along with the network packet trace confirmed that there
were no false positives. We note that there is a possibil-
ity of false negatives, which is difficult to verify with the
lack of the source code of the third-party applications.
Summary: Our study of 30 popular applications shows
the effectiveness of the TaintDroid system in accu-
rately tracking applications’ use of privacy sensitive data.
While monitoring these applications, TaintDroid gener-
ated no false positives (with the exception of the IMSI
taint source which we disabled for experiments, see Sec-
tion 8). The flags raised by TaintDroid helped to identify
potential privacy violations by the tested applications.
Half of the studied applications share location data with
advertisement servers. Approximately one third of the
applications expose the device ID, sometimes with the
phone number and the SIM card serial number. The anal-
ysis was simplified by the taint tag provided by Taint-
Droid that precisely describes which privacy relevant
data is included in the payload, especially for binary pay-
loads. We also note that there was almost no perceived
latency while running experiments with TaintDroid.
7 Performance Evaluation
We now study TaintDroid’s taint tracking overhead.
Experiments were performed on a Google Nexus One
running Android OS version 2.1 modified for TaintDroid.
Within the interpreted environment, TaintDroid incurs
the same performance and memory overhead regardless
of the existence of taint markings. Hence, we only need
to ensure file access includes appropriate taint tags.
7.1 Macrobenchmarks
During the application study, we anecdotally observed
limited performance overhead. We hypothesize that this
is because: 1) most applications are primarily in a “wait
state,” and 2) heavyweight operations (e.g., screen up-
dates and webpage rendering) occur in unmonitored na-
tive libraries.
To gain further insight into perceived overhead, we
devised five macrobenchmarks for common high-level
smartphone operations. Each experiment was measured
50 times and observed 95% confidence intervals at least
an order of magnitude less than the mean. In each case,
we excluded the first run to remove unrelated initializa-
tion costs. Experimental results are shown in Table 4.
Application Load Time: The application load time
measures from when Android’s Activity Manager re-
USENIX Association
USENIX Association
Table 4: Macrobenchmark Results
Android | TaintDroid
App Load Time 63 ms 65 ms
Address Book (create) 348 ms 367 ms
Address Book (read) 101 ms 119 ms
Phone Call 96 ms 106 ms
Take Picture 1718 ms 2216 ms
ceives a command to start an activity component to the
time the activity thread is displayed. This time includes
application resolution by the Activity Manager, IPC, and
graphical display. TaintDroid adds only 3% overhead, as
the operation is dominated by native graphics libraries.
Address Book: We built a custom application to create,
read, and delete entries for the phone’s address book, ex-
ercising both file read and write. Create used three SQL
transactions while read used two SQL transactions. The
subsequent delete operation was lazy, returning in O ms,
and hence was excluded from our results. TaintDroid
adds approximately 5.5% and 18% overhead for address
book entry creates and reads, respectively. The addi-
tional overhead for reads can be attributed to file taint
propagation. The data is not tainted before create, hence
no file propagation is needed. Note that the user experi-
ences less than 20 ms overhead when creating or viewing
a contact.
Phone Call: The phone call benchmark measured the
time from pressing “dial” to the point at which the audio
hardware was reconfigured to “in call” mode. TaintDroid
only adds 10 ms per phone call setup (~ 10% overhead),
which is significantly less than call setup in the network,
which takes on the order of seconds.
Take Picture: The picture benchmark measures from
the time the user presses the “take picture” button un-
til the preview display is re-enabled. This measurement
includes the time to capture a picture from the camera
and save the file to the SDcard. TaintDroid adds 498 ms
to the 1718 ms needed by Android to take a picture (an
overhead of 29%). A portion of this overhead can be at-
tributed to to additional file operations required for taint
propagation (one getxattr/setxattr pair per written data
buffer). Note that some of this overhead can be reduced
by eliminating redundant taint propagation. That is, only
the taint tag for the first data buffer written to file needs to
be propagated. For example, the current taint tag could
be associated with the file descriptor.
7.2 Java Microbenchmark
Figure 5 shows the execution time results of a Java mi-
crobenchmark. We used an Android port of the standard
CaffeineMark 3.0 [43]. CaffeineMark uses an internal
scoring metric only useful for relative comparisons.
The results are consistent with implementation-
2000 1 1 1
Android ==
1800 F TaintDroid ===
1600 } |
1400 + |
1200 + |
1000 + |
800 F 4
CaffeineMark 3.0 Score
600 + 7
400 + 4
200 + 4
sieve loop logic string float method Overall
CaffeineMark 3.0 Benchmark
Figure 5: Microbenchmark of Java overhead. Error bars
indicate 95% confidence intervals.
specific expectations. The overhead incurred by Taint-
Droid is smallest for the benchmarks dominated by arith-
metic and logic operations. The taint propagation for
these operations is simple, consisting of an additional
copy of spatially local memory. The string benchmark,
on the other hand, experiences the greatest overhead.
This is most likely due to the additional memory com-
parisons that occur when the JNI propagation heuristic
checks for string objects in method prototypes.
The “overall” results indicate cumulative score across
individual benchmarks. CaffeineMark documentation
states that scores roughly correspond to the number of
Java instructions executed per second. Here, the unmod-
ified Android system had an average score of 1121, and
TaintDroid measured 967. TaintDroid has a 14% over-
head with respect to the unmodified system.
We also measured memory consumption during the
CaffeineMark benchmark. The benchmark consumed
21.28 MB on the unmodified system and 22.21 MB while
running on TaintDroid, indicating a 4.4% memory over-
head. Note that much of an Android process’s memory
is used by the zygote runtime environment. These na-
tive library memory pages are shared between applica-
tions to reduce the overall system memory footprint and
require taint tracking. Given that TaintDroid stores 32
taint markings (4 bytes) for each 32-bit variable in the
interpreted environment (regardless of taint state), this
overhead is expected.
7.3. IPC Microbenchmark
The IPC benchmark considers overhead due to the par-
cel modifications. For this experiment, we developed
client and service applications that perform binder trans-
actions as fast as possible. The service manipulates ac-
count objects (a username string and a balance integer)
and provides two interfaces: setAccount() and getAc-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) 403
404
Table 5: IPC Throughput Test (10,000 msgs).
Android | TaintDroid
Time (s) 8.58 10.89
Memory (client) 21.06MB 21.88MB
Memory (service) | 18.92MB 19.48MB
count(). The experiment measures the time for the client
to invoke each interface pair 10,000 times.
Table 5 summarizes the results of the IPC benchmark.
TaintDroid was 27% slower than Android. TaintDroid
only adds four bytes to each IPC object, therefore over-
head due to data size is unlikely. The more likely cause of
the overhead is the continual copying of taint tags as val-
ues are marshalled into and out of the parcel byte buffer.
Finally, TaintDroid used 3.5% more memory than An-
droid, which is comparable to the consumption observed
during the CaffeineMark benchmarks.
8 Discussion
Approach Limitations: TaintDroid only tracks data
flows (i.e., explicit flows) and does not track control
flows (i.e., implicit flows) to minimize performance over-
head. Section 6 shows that TaintDroid can track applica-
tions’ expected data exposure and also reveal suspicious
actions. However, applications that are truly malicious
can game our system and exfiltrate privacy sensitive in-
formation through control flows. Fully tracking control
flow requires static analysis [14, 37], which is not appli-
cable to analyzing third-party applications whose source
code is unavailable. Direct control flows can be tracked
dynamically if a taint scope can be determined [53];
however, DEX does not maintain branch structures that
TaintDroid can leverage. On-demand static analysis to
determine method control flow graphs (CFGs) provides
this context [39]; however, TaintDroid does not currently
perform such analysis in order to avoid false positives
and significant performance overhead. Our data flow
taint propagation logic is consistent with existing, well
known, taint tracking systems [7, 57]. Finally, once in-
formation leaves the phone, it may return in a network
reply. TaintDroid cannot track such information.
Implementation Limitations: Android uses the Apache
Harmony [3] implementation of Java with a few custom
modifications. This implementation includes support for
the PlatformAddress class, which contains a native ad-
dress and is used by DirectBuffer objects. The file and
network IO APIs include write and read “direct” vari-
ants that consume the native address from a DirectBuffer.
TaintDroid does not currently track taint tags on Direct-
Buffer objects, because the data is stored in opaque native
data structures. Currently, TaintDroid logs when a read
or write “direct” variant is used, which anecdotally oc-
curred with minimal frequency. Similar implementation
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
limitations exist with the sun.misc.Unsafe class, which
also operates on native addresses.
Taint Source Limitations: While TaintDroid is very ef-
fective for tracking sensitive information, it causes sig-
nificant false positives when the tracked information con-
tains configuration identifiers. For example, the IMSI nu-
meric string consists of a Mobile Country Code (MCC),
Mobile Network Code (MNC), and Mobile Station Iden-
tifier Number (MSIN), which are all tainted together.>
Android uses the MCC and MNC extensively as con-
figuration parameters when communicating other data.
This causes all information in a parcel to become tainted,
eventually resulting in an explosion of tainted informa-
tion. Thus, for taint sources that contain configuration
parameters, tainting individual variables within parcels
is more appropriate. However, as our analysis results in
Section 6 show, message-level taint tracking is effective
for the majority of our taint sources.
9 Related Work
Mobile phone host security is a growing concern.
OS-level protections such as Kirin [18], Saint [42],
and Security-by-Contract [15] provide enhanced security
mechanisms for Android and Windows Mobile. These
approaches prevent access to sensitive information; how-
ever, once information enters the application, no addi-
tional mediation occurs. In systems with larger displays,
a graphical widget [27] can help users visualize sensor
access policies. Mulliner et al. [36] provide information
tracking by labeling smartphone processes based on the
interfaces they access, effectively limiting access to fu-
ture interfaces based on acquired labels.
Decentralized information flow control (DIFC) en-
hanced operating systems such as Asbestos [52] and HiS-
tar [60] label processes and enforce access control based
on Denning’s lattice model for information flow secu-
rity [13]. Flume [30] provides similar enhancements for
legacy OS abstractions. DEFCon [34] uses a logic simi-
lar to these DIFC OSes, but focuses on events and modi-
fies a Java runtime with lightweight isolation. Related to
these system-level approaches, PRECIP [54] labels both
processes and shared kernel objects such as the clipboard
and display buffer. However, these process-level infor-
mation flow models are coarse grained and cannot track
sensitive information within untrusted applications.
Tools that analyze applications for privacy sensi-
tive information leaks include Privacy Oracle [28] and
TightLip [59]. These tools investigate applications while
treating them as a black box, thus enabling analysis of
off-the-shelf applications. However, this black-box anal-
ysis tool becomes ineffective when applications use en-
cryption prior to releasing sensitive information.
Language-based information flow security [46] ex-
tends existing programming languages by labeling vari-
USENIX Association
USENIX Association
ables with security attributes. Compilers use the secu-
rity labels to generate security proofs, e.g., Jif [37, 38]
and SLam [24]. Laminar [45] provides DIFC guarantees
based on programmer defined security regions. However,
these languages require careful development and are of-
ten incompatible with legacy software designs [25].
Dynamic taint analysis provides information track-
ing for legacy programs. The approach has been used
to enhance system integrity (e.g., defend against soft-
ware attacks [41, 44, 8]) and confidentiality (e.g., dis-
cover privacy exposure [57, 16, 61]), as well as track
Internet worms [9]. Dynamic tracking approaches
range from whole-system analysis using hardware exten-
sions [51, 11, 50] and emulation environments [7, 57]
to per-process tracking using dynamic binary transla-
tion (DBT) [6, 44, 8, 61]. The performance and mem-
ory overhead associated with dynamic tracking has re-
sulted in an array of optimizations, including optimizing
context switches [44], on-demand tracking [26] based
on hypervisor introspection, and function summaries for
code with known information flow properties [61]. If
source code is available, significant performance im-
provements can be achieved by automatically instru-
menting legacy programs with dynamic tracking func-
tionality [56, 31]. Automatic instrumentation has also
been performed on x86 binaries [47], providing a com-
promise between source code translation and DBT. Our
TaintDroid design was inspired by these prior works, but
addressed different challenges unique to mobile phones.
To our knowledge, TaintDroid is the first taint tracking
system for a mobile phone and is the first dynamic taint
analysis system to achieve practical system-wide analy-
sis through the integration of tracking multiple data ob-
ject granularities.
Finally, dynamic taint analysis has been applied to vir-
tual machines and interpreters. Haldar et al. [22] in-
strument the Java String class with taint tracking to pre-
vent SQL injection attacks. WASP [23] has similar mo-
tivations; however, it uses positive tainting of individ-
ual characters to ensure the SQL query contains only
high-integrity substrings. Chandra and Franz [5] pro-
pose fine-grained information flow tracking within the
JVM and instrument Java byte-code to aid control flow
analysis. Similarly, Nair et al. [39] instrument the Kaffe
JVM. Vogt et al. [53] instrument a Javascript interpreter
to prevent cross-site scripting attacks. Xu et al. [56] au-
tomatically instrument the PHP interpreter source code
with dynamic information tracking to prevent SQL in-
jection attacks. Finally, the Resin [58] environment for
PHP and Python uses data flow tracking to prevent an as-
sortment of Web application attacks. When data leaves
the interpreted environment, Resin implements filters
for files and SQL databases to serialize and de-serialize
objects and policy with byte-level granularity. Taint-
Droid’s interpreted code taint propagation bears similar-
ity to some of these works. However, TaintDroid im-
plements system-wide information flow tracking, seam-
lessly connecting interpreter taint tracking with a range
of operating system sharing mechanisms.
10 Conclusions
While some mobile phone operating systems allow
users to control applications’ access to sensitive informa-
tion, such as location sensors, camera images, and con-
tact lists, users lack visibility into how applications use
their private data. To address this, we present TaintDroid,
an efficient, system-wide information flow tracking tool
that can simultaneously track multiple sources of sensi-
tive data. A key design goal of TaintDroid is efficiency,
and TaintDroid achieves this by integrating four gran-
ularities of taint propagation (variable-level, message-
level, method-level, and file-level) to achieve a 14% per-
formance overhead on a CPU-bound microbenchmark.
We also used our TaintDroid implementation to study
the behavior of 30 popular third-party applications, cho-
sen at random from the Android Marketplace. Our study
revealed that two-thirds of the applications in our study
exhibit suspicious handling of sensitive data, and that 15
of the 30 applications reported users’ locations to remote
advertising servers. Our findings demonstrate the effec-
tiveness and value of enhancing smartphone platforms
with monitoring tools such as TaintDroid.
Acknowledgments
We would like to thank Intel Labs, Berkeley and
Seattle for its support and feedback during the design
and prototype implementation of this work. We thank
Jayanth Kannon, Stuart Schechter, and Ben Greenstein
for their feedback during the writing of this paper. We
also thank Kevin Butler, Stephen McLaughlin, Machigar
Ongtang, and the SIIS lab as a whole for their helpful
comments. This material is based upon work supported
by the National Science Foundation. William Enck and
Patrick McDaniel were partially supported by NSF Grant
No. CNS-0905447, CNS-0721579 and CNS-0643907.
Landon Cox and Peter Gilbert were partially supported
by NSF CAREER Award CNS-0747283. Any opinions,
findings, and conclusions or recommendations expressed
in this material are those of the author(s) and do not nec-
essarily reflect the views of the National Science Foun-
dation.
References
[1] Android. http: //www.android.com.
[2] Android Market. http: //market.android.com.
[3] Apache Harmony — Open Source Java Platform.
harmony.apache.org.
http://
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) 405
406
[4]
[5]
[6
=
[7]
[8
=
[9]
[10]
(11)
[12]
[13]
[14]
[15]
[16]
[17]
[18]
[19]
[20]
APPLE, INC. Apples App Store Downloads Top Three
Billion. http://www.apple.com/pr/library/2010/
01/05appstore.html, January 2010.
CHANDRA, D., AND FRANZ, M._ Fine-Grained Information
Flow Analysis and Enforcement in a Java Virtual Machine. In
Proceedings of the 23rd Annual Computer Security Applications
Conference (ACSAC) (December 2007).
CHENG, W., ZHAO, Q., YU, B., AND HIROSHIGE, S. Taint-
Trace: Efficient Flow Tracing with Dyanmic Binary Rewriting.
In Proceedings of the IEEE Symposium on Computers and Com-
munications (ISCC) (June 2006), pp. 749-754.
CuHow, J., PFAFF, B., GARFINKEL, T., CHRISTOPHER, K.,
AND ROSENBLUM, M. Understanding Data Lifetime via Whole
System Simulation. In Proceedings of the 13th USENIX Security
Symposium (August 2004).
CLAUSE, J., L1, W., AND ORSO, A. Dytan: A Generic Dy-
namic Taint Analysis Framework. In Proceedings of the 2007 in-
ternational symposium on Software testing and analysis (2007),
pp. 196-206.
Costa, M., CROWCROFT, J., CASTRO, M., ROWSTRON, A.,
ZHOU, L., ZHANG, L., AND BARHAM, P. Vigilante: End-to-
End Containment of Internet Worms. In Proceedings of the ACM
Symposium on Operating Systems Principles (2005).
Cox, L. P., AND GILBERT, P. RedFlag: Reducing Inadvertent
Leaks by Personal Machines. Tech. Rep. TR-2009-02, Duke Uni-
versity, 2009.
CRANDALL, J. R., AND CHONG, F. T. Minos: Control Data
Attack Prevention Orthogonal to Memory Model. In Proceedings
of the International Symposium on Microarchitecture (December
2004), pp. 221-232.
DAVIES, C. iPhone spyware debated as app _li-
brary “phones home”. http://www.slashgear.
com/iphone-spyware-debated-as-—app-
library-phones-home-1752491/, August 17, 2009.
DENNING, D. E. A Lattice Model of Secure Information Flow.
Communications of the ACM 19, 5 (May 1976), 236-243.
DENNING, D. E., AND DENNING, P. J. Certification of Pro-
grams for Secure Information Flow. Communications of the ACM
20, 7 (July 1977).
DESMET, L., JOOSEN, W., MASSACCI, F., PHILIPPAERTS,
P., PIESSENS, F., SIAHAAN, I., AND VANOVERBERGHE, D.
Security-by-contract on the .NET platform. Information Security
Technical Report 13, 1 (January 2008), 25-32.
EGELE, M., KRUEGEL, C., KIRDA, E., YIN, H., AND SONG,
D. Dyanmic Spyware Analysis. In Proceedings of the USENIX
Annual Technical Conference (June 2007), pp. 233-246.
ENCK, W., GILBERT, P., CHUN, B.-G., Cox, L. P., JUNG,
J., MCDANIEL, P., AND SHETH, A. N._ TaintDroid: An
Information-Flow Tracking System for Realtime Privacy Mon-
itoring on Smartphones. Tech. Rep. NAS-TR-0120-2010, Net-
work and Security Research Center, Department of Computer
Science and Engineering, Pennsylvania State University, Univer-
sity Park, PA, USA, August 2010.
ENCK, W., ONGTANG, M., AND MCDANIEL, P. On
Lightweight Mobile Phone Application Certification. In Proceed-
ings of the 16th ACM Conference on Computer and Communica-
tions Security (CCS) (November 2009).
FITZPATRICK, M. Mobile that allows bosses to snoop on staff
developed. BBC News, March 2010. http://news.bbc.
co.uk/2/hi/technology/8559683.stm.
Flurry Mobile Application Analytics. http://www. flurry.
com/product/technical-info.html.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
[21]
[22]
[23]
[24]
[25]
[26]
[27]
[28]
[29]
[30]
[31]
[32]
[33]
[34]
[35]
[36]
[37]
Google Maps for Mobile. http://www.google.com/
mobile/products/maps.html.
HALDAR, V., CHANDRA, D., AND FRANZ, M. Dynamic
Taint Propagation for Java. In Proceedings of the 21st Annual
Computer Security Applications Conference (ACSAC) (Decem-
ber 2005), pp. 303-311.
HALFOND, W. G., ORSO, A., AND MANOLIOS, P. WASP:
Protecting Web Applications Using Positive Tainting and Syntax-
Aware Evaluation. IEEE Transactions on Software Engineering
34, 1 (2008), 65-81.
HEINTZE, N., AND RIECKE, J. G. The SLam Calculus: Pro-
gramming with Secrecy and Integrity. In Proceedings of the
Symposium on Principles of Programming Languages (POPL)
(1998), pp. 365-377.
Hicks, B., AHMADIZADEH, K., AND MCDANIEL, P. Under-
standing practical application development in security-typed lan-
guages. In 22st Annual Computer Security Applications Confer-
ence (ACSAC) (2006), pp. 153-164.
Ho, A., FETTERMAN, M., CLARK, C., WARFIELD, A., AND
HAND, S. Practical Taint-Based Protection using Demand Emu-
lation. In Proceedings of the European Conference on Computer
Systems (EuroSys) (2006), pp. 29-41.
HOWELL, J., AND SCHECHTER, S. What You See is What they
Get: Protecting users from unwanted use of microphones, cam-
era, and other sensors. In Proceedings of Web 2.0 Security and
Privacy Workshop (2010).
JUNG, J., SHETH, A., GREENSTEIN, B., WETHERALL, D.,
MAGANIS, G., AND KOHNO, T. Privacy Oracle: A System for
Finding Application Leaks with Black Box Differential Testing.
In Proceedings of ACM CCS (2008).
KING, D., HICKS, B., HICKS, M., AND JAEGER, T. Implicit
Flows: Can’t Live with ’Em, Can’t Live without ’Em. In Pro-
ceedings of the International Conference on Information Systems
Security (2008).
KROHN, M., Yip, A., BRODSKY, M., CLIFFER, N.,
KAASHOEK, M. F., KOHLER, E., AND MORRIS, R. Informa-
tion Flow Control for Standard OS Abstractions. In Proceedings
of ACM Symposium on Operating Systems Principles (2007).
LAM, L. C., AND CKER CHIUEH, T. A General Dynamic Infor-
mation Flow Tracking Framework for Security Applications. In
Proceedings of the Annual Computer Security Applications Con-
ference (ACSAC) (2006).
LIANG, S. Java Native Interface: Programmer’s Guide and
Specification. Prentice Hall PTR, 1999.
LOOKOUT. Introducing the App Genome
http://blog.mylookout.com/2010/07/
int roducing-the-app-genome-project/,
2010.
MIGLIAVACCA, M., PAPAGIANNIS, I., EYERS, D. M., SHAND,
B., BACON, J., AND PIETZUCH, P. DEFCon: High-Performance
Event Processing with Information Security. In PROCEEDINGS
of the USENIX Annual Technical Conference (2010).
MOREN, D. Retrievable iPhone numbers mean potential
privacy issues. http://www.macworld.com/article/
143047/2009/09/phone_hole.html, September 29,
2009.
MULLINER, C., VIGNA, G., DAGON, D., AND LEE, W. Us-
ing Labeling to Prevent Cross-Service Attacks Against Smart
Phones. In Proceedings of Detection of Intrusions and Malware
& Vulnerability Assessment (DIMVA) (2006).
My ERs, A. C. JFlow: Practical Mostly-Static Information Flow
Control. In Proceedings of the ACM Symposium on Principles of
Programming Langauges (POPL) (January 1999).
Project.
July
USENIX Association
[38]
[39]
[40]
[41]
[42]
[43]
[44]
[45]
[46]
[47]
[48]
[49]
[50]
[51]
[52]
USENIX Association
Myers, A. C., AND LISKOV, B. Protecting Privacy Using the
Decentralized Label Model. ACM Transactions on Software En-
gineering and Methodology 9, 4 (October 2000), 410-442.
NAIR, S. K., SIMPSON, P. N., CRISPO, B., AND TANENBAUM,
A. S. A Virtual Machine Based Information Flow Control Sys-
tem for Policy Enforcement. In the Ist International Workshop
on Run Time Enforcement for Mobile and Distributed Systems
(REM) (2007).
NEWSOME, J., MCCAMANT, S., AND SONG, D. Measuring
channel capacity to distinguish undue influence. In ACM SIG-
PLAN Workshop on Programming Languages and Analysis for
Security (2009).
NEWSOME, J., AND SONG, D. Dynamic Taint Analysis for
Automatic Detection, Analysis, and Signature Generation of Ex-
ploits on Commodity Software. In Proc. of Network and Dis-
tributed System Security Symposium (2005).
ONGTANG, M., MCLAUGHLIN, S., ENCK, W., AND MC-
DANIEL, P. Semantically Rich Application-Centric Security in
Android. In Proceedings of the 25th Annual Computer Security
Applications Conference (ACSAC) (2009).
PENDRAGON SOFTWARE CORPORATION. CaffeineMark 3.0.
http://www. benchmarkhq.ru/cm30/.
QIN, F., WANG, C., LI, Z., SEOP KIM, H., ZHOU, Y., AND
Wu, Y. LIFT: A Low-Overhead Practical Information Flow
Tracking System for Detecting Security Attacks. In Proceedings
of the 39th Annual IEEE/ACM International Symposium on Mi-
croarchitecture (2006), pp. 135-148.
Roy, I., PORTER, D. E., BOND, M. D., MCKINLEY, K. S.,
AND WITCHEL, E. Laminar: Practical Fine-Grained Decentral-
ized Information Flow Control. In Proceedings of Programming
Language Design and Implementation (2009).
SABELFELD, A., AND MYERS, A. C. Language-based
information-flow security. [EEE Journal on Selected Areas in
Communication 21, 1 (January 2003), 5-19.
SAXENA, P., SEKAR, R., AND PURANIK, V. Efficient Fine-
Grained Binary Instrumentation with Applications to Taint-
Tracking. In Proceedings of the IEEE/ACM symposium on Code
Generation and Optimization (CGO) (2008).
SCHWARTZ, E. J., AVGERINOS, T., AND BRUMLEY, D. All
You Ever Wanted to Know about Dynamic Taint Analysis and
Forward Symbolic Execution (but might have been afraid to ask).
In JEEE Symposium on Security and Privacy (2010).
SLOWINSKA, A., AND Bos, H. Pointless Tainting? Evaluating
the Practicality of Pointer Tainting. In Proceedings of the Euro-
pean Conference on Computer Systems (EuroSys) (April 2009),
pp. 61-74.
SUH, G. E., LEE, J. W., ZHANG, D., AND DEVADAS, S. Se-
cure Program Execution via Dynamic Information Flow Track-
ing. In Proceedings of Architectural Support for Programming
Languages and Operating Systems (2004).
VACHHARAJANI, N., BRIDGES, M. J., CHANG, J., RANGAN,
R., OTTONI, G., BLOME, J. A., REIS, G. A., VACHHARA-
JANI, M., AND AUGUST, D. I. RIFLE: An Architectural Frame-
work for User-Centric Information-Flow Security. In Proceed-
ings of the 37th annual IEEE/ACM International Symposium on
Microarchitecture (2004), pp. 243-254.
VANDEBOGART, S., EFSTATHOPOULOS, P., KOHLER, E.,
KROHN, M., FREY, C., ZIEGLER, D., KAASHOEK, F., MOR-
RIS, R., AND MAZIERES, D. Labels and Event Processes in
the Asbestos Operating System. ACM Transactions on Computer
Systems (TOCS) 25, 4 (December 2007).
[53] VocT, P., NENTWICH, F., JOVANOVIC, N., KIRDA, E.,
KRUEGEL, C., AND VIGNA, G. Cross-Site Scripting Preven-
tion with Dynamic Data Tainting and Static Analysis. In Proc. of
Network & Distributed System Security (2007).
[54] WANG, X., LI, Z., LI, N., AND CHOI, J. Y. PRECIP: Towards
Practical and Retrofittable Confidential Information Protection.
In Proceedings of 15th Network and Distributed System Security
Symposium (NDSS) (2008).
[55] WhatApp. http://www.whatapp.org. Accessed April
2010.
[56] XU, W., BHATKAR, S., AND SEKAR, R. Taint-Enhanced Pol-
icy Enforcement: A Practical Approach to Defeat a Wide Range
of Attacks. In Proceedings of the USENIX Security Symposium
(August 2006), pp. 121-136.
[57] YIN, H., SONG, D., EGELE, M., KRUEGEL, C., AND KIRDA,
E. Panorama: Capturing System-wide Information Flow for Mal-
ware Detection and Analysis. In Proceedings of ACM Computer
and Communications Security (2007).
[58] Yip, A., WANG, X., ZELDOVICH, N., AND KAASHOEK, M. F.
Improving Application Security with Data Flow Assertions. In
Proceedings of the ACM Symposium on Operating Systems Prin-
ciples (Oct. 2009).
[59] YUMEREFENDI, A. R., MICKLE, B., AND Cox, L. P. TightLip:
Keeping Applications from Spilling the Beans. In Proceedings
of the 4th USENIX Symposium on Network Systems Design &
Implementation (NSDI) (2007).
[60] ZELDOVICH, N., BOYD-WICKIZER, S., KOHLER, E., AND
MAZIERES, D. Making Information Flow Explicit in HiStar. In
Proceedings of the 7th symposium on Operating Systems Design
and Implementation (OSDI) (2006).
[61] ZHu, D., JUNG, J., SONG, D., KOHNO, T., AND WETHERALL,
D. Privacy Scope: A Precise Information Flow Tracking Sys-
tem For Finding Application Leaks. Tech. Rep. EECS-2009-145,
Department of Computer Science, UC Berkeley, 2009.
Notes
1A similar approach can be applied to just-in-time compilation by
inserting tracking code within the generated binary.
2Only 11 internal VM methods were added between versions 1.5
and 2.1 (primarily for debugging and profiling)
3There was a relatively small number of JNI methods that did not
either have a Java interface or C/C++ implementation. These unusable
methods were excluded from our survey.
“Because of the limitation of the IMSI taint source as discussed in
Section 8, we disabled the IMSI taint source for experiments. Nonethe-
less, TaintDroid’s flag of the ICC-ID and the phone number led us to
find the IMSI contained in the same payload.
Regardless of the string separation, the MCC and MNC are identi-
fiers that warrant taint sources.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) 407
USENIX Association
StarTrack Next Generation:
A Scalable Infrastructure for Track-Based Applications
Maya Haridasan, Iqbal Mohomed, Doug Terry, Chandramohan A. Thekkath, and Li Zhang
Microsoft Research Silicon Valley
Abstract
StarTrack was the first service designed to manage tracks
of GPS location coordinates obtained from mobile de-
vices and to facilitate the construction of track-based
applications. Our early attempts to build practical ap-
plications on StarTrack revealed substantial efficiency
and scalability problems, including frequent client-server
roundtrips, unnecessary data transfers, costly similar-
ity comparisons involving thousands of tracks, and poor
fault-tolerance. To remedy these limitations, we revised
the overall system architecture, API, and implementa-
tion. The API was extended to operate on collections
of tracks rather than individual tracks, delay query exe-
cution, and permit caching of query results. New data
structures, namely track trees, were introduced to speed
the common operation of searching for similar tracks.
Map matching algorithms were adopted to convert each
track into a more compact and canonical sequence of
road segments. And the underlying track database was
partitioned and replicated among multiple servers. Al-
together, these changes not only simplified the construc-
tion of track-based applications, which we confirmed by
building applications using our new API, but also re-
sulted in considerable performance gains. Measurements
of similarity queries, for example, show two to three or-
ders of magnitude improvement in query times.
1 Introduction
The easy availability of function-rich mobile devices has
fueled significant interest in the “mobile internet’, where
mobile devices access internet-based services and web
applications. Mobile devices that can determine their
own physical location are adding to this trend by facilitat-
ing the development of diverse location-based services.
In addition to individual coordinates, “tracks” — time-
ordered sequences of GPS locations recorded by mobile
devices — enable many location-oriented applications,
varying from personal applications such as trip plan-
ning and health monitoring, to social applications such
as ride-sharing and urban sensing.
StarTrack, introduced in an earlier paper, was the first
service designed to manage tracks from mobile devices
and to facilitate the construction of track-based applica-
tions [3]. That paper was primarily focussed on identi-
fying a rich class of interesting personal and social ap-
plications that exploited histories of tracks; not much
attention was paid to implementing the service at scale
or building applications. Indeed, the entire implementa-
tion relied heavily on the services of a single database
server with a thin software veneer providing an API.
No applications were built using this API. Our first at-
tempt to build realistic applications using this system re-
vealed many shortcomings: principally inadequate per-
formance, scalability, and fault-tolerance. Some of these,
e.g. fault-tolerance, arose out of inadequate system struc-
ture in the original implementation. But by far most of
the shortcomings arose out of a mismatch between the
API provided by the system and what was required by
applications. Specifically, several functions that were
necessary for applications were either missing in the API
or needed to be synthesized from lower-level primitives
of the API. This mismatch led to costly and unneces-
sary client-server communication and data transfer. In
addition to these deficiencies, our original system imple-
mented common operations inefficiently (e.g. track com-
parisons).
This paper describes how the design and implementa-
tion of StarTrack have evolved non-trivially to address
real-world issues of dealing with tracks. Our experi-
ence with track-based applications is admittedly limited.
We do not claim our API is universal or fundamental in
any sense; it will undoubtedly evolve as we encounter
new classes of applications that we have not anticipated.
Nonetheless, we believe our work and experience to date
will be beneficial to researchers and practitioners in this
rapidly growing field.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) 409
410
In general, we found managing and providing seman-
tically rich operations on tracks to be surprisingly dif-
ficult. Track queries are complex because they involve
geographic and similarity constraints, and a naive solu-
tion requiring expensive evaluation of these constraints
does not scale to real-world online demand.
The main insight we use in tackling the complexity
of tracks is to recognize that tracks tend to be repetitive.
Repetitiveness arises from two distinct sources. An indi-
vidual tends to follow substantially similar routes in his
day-to-day life. This intuition is supported scientifically
by a recent study in Science [23]. Second, the vast major-
ity of tracks are collected on roads and highways, again
leading to significant overlap in tracks even if they are
from different users.
This insight permeates all parts of our revamped Star-
Track infrastructure. We made several changes to our
system. In some cases, we needed new techniques and
data structures; in other cases, we used more established
techniques, but synthesized in novel ways, to support a
new class of track-based applications efficiently.
The changes to our system fall into four broad areas:
API Changes. All operations in our original API dealt
with individual tracks, often causing entire sets of tracks
to be moved repeatedly between the service and applica-
tions. StarTrack currently supports a “track collection”,
representing a set of tracks. Several functions in the API
now operate on and return results as track collections.
This change had several benefits. Apart from the obvi-
ous ease of programming, it afforded StarTrack opportu-
nities to optimize the performance of specific operations
through delayed and partial evaluation of these collec-
tions. Caching of both full and partial results also be-
came possible.
Changes in Track Representation. We quickly discov-
ered dealing with “raw” tracks by themselves to be in-
efficient. We now use a “canonical” representation for
tracks, where tracks are represented as a sequence of
points drawn from a fixed set, such as road intersections.
Canonicalization benefits many aspects of the system.
It reduces the computational costs of track comparison
while improving its accuracy. As a consequence of im-
proved accuracy, we are able to group a user’s similar
tracks more effectively and maintain a small set of repre-
sentative tracks that captures the essentials of a large set
of tracks. Many applications only need to operate on the
set of representative tracks, leading to significantly fewer
operations, better caching of data, and consequently, bet-
ter performance.
Changes to On-Disk and In-Memory Data Struc-
tures. The original StarTrack API was implemented as
a thin veneer on top of a geospatial database system.
While simplifying the implementation, this resulted in
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
poor performance for many operators. The changes in
the API and canonicalization described above allowed
us to build specialized in-memory data structures to aug-
ment the database tables. Operations that had low per-
formance are now optimized by using in-memory quad-
trees or a novel structure called a track tree described in
Section 3.3. In addition to these in-memory data struc-
tures, we reorganized the database layout to include a
table of representative tracks for each user (as mentioned
above) and other tables that aid in handling operations
with geographical constraints.
Structural Changes. Our original prototype consisted
of a single server process that stored tracks in a central-
ized database and implemented an API to access these
tracks. This single server implementation clearly did
not scale to a large number of tracks or provide fault-
tolerance. In the new system, a set of StarTrack server
machines connects to another set of database servers.
Applications use a StarTrack clerk, which implements
the API and makes remote procedure calls (RPCs) to the
StarTrack servers as necessary. It also deals with retrying
requests on server failures, and balances RPC requests
amongst servers.
We detail our changes further in the rest of the paper
(Sections 2—4), describe two scalable, robust, and effi-
cient applications they enabled us to build (Section 5)
and summarize their performance impact (Section 6).
2 Application Programming Interface
The interface exported by the StarTrack service has
undergone multiple revisions based on our experience
building realistic applications. This section describes the
key elements of the new application programming inter-
face; space restrictions prevent us from describing the
complete API.
2.1 Track Collections
The new StarTrack interface supports the notion of a
track collection, an abstract grouping of tracks, where
the application supplies the criteria for grouping. Track
collections can, in turn, participate in other StarTrack op-
erations. All non-trivial operations in the StarTrack API
take a track collection as an argument.
Track collections have two significant advantages:
Implementation Efficiency. They allow the server to
treat the set of tracks that are repeatedly accessed to-
gether as a single entity for the purposes of caching.
They also allow the server to construct specialized data
structures that operate exclusively on these tracks, mak-
ing these operations more efficient. Furthermore, by hav-
ing applications and the service refer to a potentially
USENIX Association
USENIX Association
large collection of track identifiers by a single identifier,
we reduce the communication costs of transmitting the
identities of individual tracks between them.
Programming Convenience. Applications often want
to constrain operations to tracks that belong to a particu-
lar community or cohort. For example, a social applica-
tion might wish to operate on the tracks of a user and his
group of friends. Track collections allow such an appli-
cation to create an aggregation of the tracks in which it is
interested and enable it to operate on such groups more
conveniently.
Track collections are created by using the MakeCol-
lection procedure (see API Fragment 2.1). MakeCollec-
tion takes as its first argument a set of criteria to select
a group of tracks from all tracks in the system. Individ-
ual criteria can be composed out of three elements: ge-
ographic, time, user. The first two elements have fairly
simple semantics: a geographic element is specified by a
physical geographical region and a time element is spec-
ified by a time interval. The user element consists of two
subfields: a unique identifier that specifies the user and a
string field that specifies an XPATH query. The query is
applied to the user metadata that is stored in the track by
the application.
TrackCollxn MakeCollection(GrpCriteria[] gCrit,
bool unique) ;
API fragment 2.1: Operation to create a track collec-
tion.
The second argument is a boolean that indicates
whether the system should return only “unique” tracks.
Two canonical tracks are considered unique if their start-
ing points (as well as ending points) are “close” to each
other, and their paths are highly “similar” to each other.
Similarity is more precisely defined below when we dis-
cuss the GetSimilarTracks function. Parameters that de-
cide if the start/end points are “close” to one another and
if tracks are highly similar are defined by the infrastruc-
ture. These are described further in Section 4.1.
We provide applications the option to specify the
unique flag for two reasons. People tend to travel the
same routes habitually, leading to multiple highly similar
tracks that only differ in time. Meanwhile, many applica-
tions are only interested in distinct routes without requir-
ing knowledge of the precise times at which the route was
traveled. These applications greatly benefit from using
MakeCollection with the unique flag set to true since it
significantly reduces the number of tracks in the returned
collection. If instead an application needs per track infor-
mation, for instance, if it needs to know how fast the user
travels on a particular road segment, setting unique to
false will retrieve all the relevant tracks with detailed in-
formation.
Two simple code segments calling MakeCollection are
shown in Examples 2.1 and 2.2. The first example col-
lects the tracks of user Uriah between 8AM and 10AM.
The second shows how metadata information is used to
create a track collection of all employees of an organiza-
tion.
Example 2.1 Uriah’s tracks between 8AM and 10AM.
GrpCriteria[] gCrit = new GrpCriteria[2];
UserCriteria uc = new UserCriteria();
uc.Username = "Uriah";
TimeCriteria tc = new TimeCriteria();
tc.StartHour = 8; tc.EndHour = 10;
gCrit[0O] = uc; gCrit[1] = tc;
TrackCollxn tcUriah;
tcUriah = MakeCollection(gCrit, false);
Example 2.2 Tracks of all employees of the Wickfield
corporation. The metadata string is an XPATH query,
shown here in simplified syntax for formatting reasons.
GrpCriteria[] gCrit = new GrpCriteria[1];
UserCriteria uc = new UserCriteria();
uc.metadata = ‘‘Employer = Wickfield’’;
gCrit[0] = uc;
TrackCollxn tcWField;
tcWField = MakeCollection(gCrit, true);
2.2 Manipulating Tracks
Tracks can be manipulated in several ways; we describe
a few representative operations. We have chosen these
because they embody the most significant changes we
made to the original prototype. Other operations are es-
sentially unchanged from our previous API.
JoinTrkCollections takes two or more track collections
and creates a new track collection that is the union of all
the constituent tracks. The second argument allows the
resulting track collection to retain only unique tracks.
SortTracks takes a track collection and orders the con-
stituent tracks in the collection according to one of a set
of predefined attributes. Examples of attributes we have
implemented are LENGTH and FREQ, which refer to the
length of the track and its frequency of occurrence within
that track collection.
Many track-based applications need to determine
whether tracks are similar to one another. Given two
tracks, we define track similarity as the ratio of the length
of all the segments that are common to both of them di-
vided by the length of the union of all segments present
in either of them (Figure 1(a)). GetSimilarTracks is given
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI’10) = 411
412
(a) (b) (c)
Figure |: (a) the similarity between tracks A and B is 1
and between A and D is (11 + lg + 13)/(l, + lo +13 +
la tls +lg +19), where l; is the length of segment s;; (b)
A,B,C are the tracks that pass by the areas R, and Ro;
(c) S is the common segment of A,B,C,D with frequency
threshold set to 0.6.
a track collection and a reference track and selects from
within the collection all tracks that are similar to the ref-
erence track. The returned track collection is sorted by
similarity. The degree of similarity is controlled by the
third parameter.
Track-based applications can find tracks that pass
within close proximity of a location by calling GetPass-
ByTracks. GetPassByTracks is given a track collection
and an array of Area objects and returns all tracks in the
collection that pass through all the areas (Figure 1(b)).
GetCommonSegments takes a track collection and a
frequency threshold and returns the road segments shared
by at least that fraction of the tracks in the collection.
These road segments are merged into the smallest num-
ber of contiguous routes possible (see Figure 1(c)). This
operation is useful for the application to retrieve a suc-
cinct summary of a potentially large set of tracks.
Tracks within a TrackCollxn object can be re-
trieved via the following two functions (See API Frag-
ment 2.3). GetTrackCount returns the number of tracks
in a track collection, and GetTracks returns count
tracks beginning at the start location within a track
collection.
3 StarTrack Server Design
This section describes three changes to the StarTrack
server design that we consider most significant.
3.1 Canonicalization of Tracks
In our first implementation, we stored users’ latitude and
longitude coordinates directly in the system. While this
design choice was intuitive and useful in some circum-
stances, it was problematic in many others. Recall that
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
TrackCollxn JoinTrkCollections(TrkCollxn tCs[],
bool unique) ;
TrackCollxn SortTracks (TrkCollxn tC,
SortAttribute attr);
TrackCollxn GetSimilarTracks (TrkCollxn tC,
Trk refTrk, float simThresh) ;
TrackCollxn GetPassByTracks(TrkCollxn tC,
Area[] areas);
TrackCollxn GetCommonSegments (TrkCollxn tC,
float freqThresh) ;
API fragment 2.2: Operations to manipulate a track col-
lection.
int GetTrackCount (TrkCollxn tC);
Track[] GetTracks(TrkCollxn tC, int start,
int count);
API fragment 2.3: Retrieval operations on a track col-
lection.
coordinates are samples of a path taken by a user. The
same path taken by different users may be sampled at
different points. Also, sampling is inherently error-prone
due to limitations in current localization techniques [8].
For these reasons, two identical paths can lead to widely
different sampled coordinates, making it difficult to clas-
sify them as equal. In the new system, we “canonical-
ize” paths to eliminate spurious variability in the sam-
pled coordinates. In this context, canonicalization means
that we convert a path to another path that only passes
through a set of “standard” points drawn from a (large)
fixed set. We refer to the portion of the path between two
such points as a segment.
There are several methods to canonicalize tracks. One
intuitive way is to overlay a fixed grid on the geographic
region and to map each coordinate to a grid intersection
point. A variation on this technique is to pick a suitably
weighted interior point within the grid instead of a cor-
ner.
A fundamental shortcoming of approaches based on a
fixed grid is that the grid is artificially created and does
not adapt to users’ tracks. Grids may be too fine-grained,
in which case canonicalization provides no benefits, or
too coarse-grained, in which case important features of
tracks are lost.
Instead of using an artificial grid, we can often use the
more natural and adaptive grid imposed by streets and
highways. Canonicalizing based on street maps is called
map matching and is desirable in cases where roadmaps
of the region exist. A track after canonicalization is
mapped to a path in the roadmap. A path consists of
one or more street segments and is stored as a sequence
USENIX Association
USENIX Association
of the endpoints of the segment(s). StarTrack uses a
map matching approach using hidden Markov models
designed by Krumm et al. [17, 20].
The performance of canonicalization is dependent on
three factors: the sampling rate of a track (i.e., the num-
ber of GPS points in the track), the length of the track,
and the amount of GPS noise introduced into the sam-
ples. In our system, canonicalization is done offline as a
pre-processing step. Since the performance of canonical-
ization is not that critical in our system, we do not present
detailed results. With some performance tuning, Star-
Track can canonicalize a track with average trip length
of about 20 km and 400 GPS samples in under 250 ms.
Canonicalization has two key advantages that translate
into performance savings. First, StarTrack can compare
two segments for equality without using expensive geo-
graphic constraints. Equality of segments is used within
the inner loop of the procedure that finds similar tracks,
which in turn is a very common operation in applications.
Second, canonicalization tends to create larger numbers
of identical segments. This often allows us to access and
manipulate a single representative segment rather than
dealing with individual segments. It also allows Star-
Track to identify duplicate tracks more accurately and
reduces the number of tracks it needs to process for var-
ious operations.
Canonicalization based on road networks is appropri-
ate for regions that have a mature road network and a sta-
ble map. When road networks are not available, we may
utilize technologies for constructing road maps from user
tracks [5, 7].
3.2 Delayed Evaluation
We found that applications typically make several API
calls to narrow down the set of tracks they want to re-
trieve. Our implementation of the API therefore delays
the evaluation of the tracks in a track collection until
one of the two retrieval functions in API Fragment 2.3
is called. This technique saves multiple roundtrips be-
tween the StarTrack clerk and servers. Furthermore, it
allows the StarTrack server flexibility in the queries it is-
sues to the database and in the choice of data structures
it builds for different retrieval operations.
When a client invokes a MakeCollection operation, the
client-side stub marshals an efficient description of the
call arguments and a small integer representing the pro-
cedure name. We call the resulting structure a descriptor.
The stub sends the descriptor to the server, which stamps
it with the current time to capture the database contents at
that instant and returns it.1 We require that the timestamp
be in the past with respect to the time on the database
tThere are well-known ways to avoid this RPC call, but we have
chosen not to implement them for simplicity.
server. Assuming that tracks are not deleted from the sys-
tem, this guarantees that multiple evaluations of a track
collection will always return the same set of tracks.
Operations such as JoinTrkCollections, GetPopular-
Tracks, GetSimilarTracks, and GetPassByTracks create
compositions of these descriptors (at the client stub) with
no communication to the server and no additional times-
tamps. We refer to these compositions as compound de-
scriptors. These are organized as a tree, with the leaves
being a simple timestamped descriptor.
Notice that all descriptors (compound or otherwise)
contain information about the invoked function and the
arguments, which together can be used to construct a
track collection. In this sense they can be viewed as a
closure [18] or as a specialized form of a logical view
from the database literature [9].
Our use of timestamped descriptors is a tradeoff be-
tween efficiency and freshness. Timestamps imply that
the application sees data as it existed in the database at a
particular point in time, not necessarily the latest data. It
allows the StarTrack server to cache the contents of the
database in an in-memory data structure, or discard it at
will and reevaluate it later, while providing easy to un-
derstand and consistent semantics to the application. It
also allows a client to present the descriptor to a differ-
ent StarTrack server if needed for load-balancing reasons
or if the original server crashes. Re-evaluating a descrip-
tor is guaranteed to yield the same result anywhere in the
system because the operations are deterministic, and the
timestamp acts as a snapshot of the database (provided
that tracks are not deleted from the system). If freshness
is more important for an application, it can recreate the
track collection as often as needed.
The evaluation of a descriptor yields different types
of in-memory data structures. For example, the evalua-
tion of a descriptor constructed by GetSimilarTracks may
(but need not) create a data structure called a track tree.
A descriptor created by GetPassByTracks can result in a
quad-tree [10]. The results of evaluating other descrip-
tors are typically stored as a simple set of tracks.
3.3. Track Tree
In our experience, when two tracks overlap, they usually
do so on one or very few contiguous segments. We ex-
ploit this property to build a hierarchical data structure
called a track tree, which is used to speed up the retrieval
of similar tracks.
Each road segment is represented as a leaf node in a
track tree. For each leaf node, the track tree records all
tracks that contain that particular segment. Once all the
segments in a track collection are stored as leaf nodes,
pairs of nodes that refer to geographically adjacent seg-
ments are considered for merging to form interior nodes
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) = 413
414
COO) OO
Figure 2: The track tree of the set of four tracks shown
in Figure 1(a). Each node, except for leaf nodes, is anno-
tated with the set of tracks that contain it.
of the tree. Whenever there is choice of pairs of nodes to
merge, the pair that has the highest number of tracks in
common is picked. This process is continued iteratively
up the tree. When merging two nodes, all tracks belong-
ing to both children nodes are included in the parent node
as well. By this construction, each node in the track tree
represents a contiguous sequence of road segments. In
addition, the segment is more likely to be shared by mul-
tiple tracks.
Figure 2 shows the track tree for the sample four tracks
in Figure 1(a). As shown in Figure 1(a), tracks A and
B are identical and consist of segments S1, S2, S3, S4,
and $5. Tracks C and D share common segments with
A and B. Segments shared by larger numbers of tracks
are favored when merging nodes, which explains why
segments S1 to S3 are merged together, instead of other
combinations, such as $2 to S4. Using this tree, tracks
A and B can be described by one single node (S1-5),
and tracks C and D can be described by two nodes each:
Track C by S1-4 and S6-7 and Track D by S1-3 and S8-9.
Track trees are used to accelerate several API oper-
ations. In GetCommonSegments, after we identify the
road segments shared by sufficiently many tracks, as in-
dicated by the given threshold, we use a track tree to or-
ganize them into a small number of contiguous tracks.
This is done by merging up in the tree those nodes corre-
sponding to these road segments. Given the way a track
tree is constructed, this usually results in a small number
of nodes, corresponding to a small number of contiguous
tracks.
Another API operation enabled by a track tree is Get-
SimilarTracks. Implementing this function as a database
operation is inefficient because there is little match be-
tween our similarity semantics and the primitives sup-
ported by spatial databases.
With a track tree, StarTrack can quickly find a set of
tracks with a given degree of similarity to a specific track
T (See Code Segment 3.1). First, StarTrack identifies
the set of all nodes (interior and leaf) covered by T. In
order to do this, T is initially broken into smaller seg-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
ments. StarTrack then identifies the leaf nodes in the
track tree that correspond to these segments. Next, it
identifies pairs of adjacent nodes that have a common
parent node, includes the parent into the set, and iterates
until no such parent exists. These steps are encapsulated
in the function Map.
The GetSimilarTracks operator then sorts the nodes in
T by decreasing order of length. It sequentially scans
each node, examining the set of tracks containing it, and
outputs tracks that are at least simThresh similar to
the query track. This process stops when it has found
sufficiently many tracks as defined by the maxCount
parameter, or when it has examined sufficiently many
tracks. Recall that the client supplies the simThresh
parameter (as part of the GetSimilarTracks call), as well
as the maxCount parameter (as part of the GetTracks
invocation, which triggers the evaluation of the descrip-
tor). This process will not produce any false positives
(i.e., tracks that purport to be similar but are not), but it
could miss some highly similar tracks. The percentage of
such misses is quite small when the similarity threshold
is reasonably high, as our experimental results show (see
Figure 7(c) in Section 6).
Code segment 3.1 Pseudo-code for implementing
GetSimilarTracks using tracktree.
Track[] GetSimilarTracks (TrackTree trackTree,
Track T, double simThresh, int maxCount)
{
TrackTreeNode[] nodes = trackTree.Map(T) ;
SortByDescLength (nodes) ;
SortedList<Track> results;
foreach(node in nodes) {
foreach(candidate in node.tracks) {
if (T.Similarity (candidate) >=simThresh)
results .Add(candidate) ;
examinedt+;
if ((results.Count>=maxCount) | |
(examined>=6*maxCount) )
return results;
int examined = 0;
}
}
return results;
Similar to other in-memory data structures in Star-
Track, a track tree is cached in memory until evicted
under the caching policy: LRU in our implementation.
Since track collections are immutable, we do not update
data structures during their life time. However, the track
tree structure allows for efficient insertion of new tracks,
and whenever a track collection is created by building
upon an existing track collection, an existing underlying
track tree may be copied and updated.
USENIX Association
USENIX Association
4 Storage Platform Design
As previously described, we build and maintain in-
memory data-structures at the StarTrack servers, and use
a different set of database servers to store data persis-
tently. StarTrack always checks if tracks can be found in
the in-memory data-structures before fetching them from
the database.
StarTrack uses Microsoft’s SQL Server 2008, which
supports the notion of geospatial objects as a funda-
mental data type. Data is partitioned across multiple
machines, and partitions are replicated using chained
declustering [12], which provides the necessary scaling
properties as well as automatic dynamic load-balancing
and fault-tolerance.
4.1 Database Tables
The principal on-disk data structure consists of 5 tables
stored in SQL Server.
User Table. This consists of a set of records for each
user containing a unique system-assigned user identifier
and other personal information.
Track Table. Every track is assigned a unique identi-
fier, consists of a set of time-stamped latitude and lon-
gitude coordinates, and is stored in a single row in the
table. Both the raw and the canonical versions of tracks
are stored in the same table.
Representative Track Table. This table maintains a set
of representative tracks per user and allows StarTrack to
often avoid searching the larger Track Table. Each record
stores information related to a single representative track:
the canonical coordinates, the owner, and a count of how
many actual instances of this representative track exist in
the Track Table. Upon insertion of a track into the Track
Table, StarTrack checks if there exists a representative
track that matches the new track. If so, the new track
is not inserted into the Representative Track Table, but
the count of the matching representative track is incre-
mented. The count serves as indication of the popularity
of a given representative track and is used by StarTrack
operations for ranking purposes.
Two tracks are considered as matching if their start
points are within 100 m of each other, if their end points
are within 100 m of each other, and if the tracks are at
least 90% similar. The choice of these parameters is fixed
by the infrastructure and cannot be changed by individual
applications. It is based on expected errors in GPS mea-
surements, as well as cost/benefit tradeoffs, and is not as
Procrustean as one might imagine. The values chosen
determine the size of the Representative Track table —
high start/end point buffer values and low track similar-
ity values result in a smaller table of unique tracks, but
applications may lose the ability to discriminate between
tracks. The size of the table, in turn, affects the speed of
many functions in the API that must access that table.
Coordinate Table. During the map matching process,
the set of coordinates in a path is drawn from a finite list
of points, which depends on the particulars of the map
data used for canonicalization of tracks. Each record in
this table maps a location identifier to a pair of coordi-
nates. This particular table is immutable, replicated on
each database server, and not partitioned.
Coordinate to Track Table. This table maps coordi-
nates to tracks that go through them. We use it to speed
up the location of tracks that pass through certain geo-
graphic boundaries.
StarTrack allows three types of criteria in fetching
tracks from the database: user, time, and geographic
region. Region-based queries may be performed by
leveraging the geospatial functions provided by modern
database systems, which support specialized indexing
schemes. Such systems must be used with care because
costs are still significant when indexing large numbers of
complex geospatial objects such as tracks.
In the original StarTrack implementation, we used the
geospatial primitives of the database to treat each track as
a separate object and created a geospatial index over all
such objects. Now, we maintain a geospatial index on
the Coordinate Table alone, thereby reducing the number
of objects on which the geospatial index is maintained.
We use this index to find all locations that match a given
geographic query. We then use the Coordinate To Track
Table to look up all tracks that go through these locations.
This is feasible precisely because of the canonicalization
pre-processing step.
The Coordinate Table and its geospatial index are
maintained by the database server and portions of them
may be cached in memory. We present a comparison
of the original and new approaches in Section 6.2 (Fig-
ure 3). If necessary we can further speed up our design
by not storing the Coordinate Table in the database server
and can instead store it in memory and index it using an
in-memory quad-tree.
4.2 Database Server Organization
The tables mentioned above are partitioned across multi-
ple database servers. Based on StarTrack’s search crite-
ria options, we considered two partitioning schemes: by
geography and by user identifier.
We decided not to partition by geography, since over
time it would lead to increasing numbers of tracks
that span geographic regions, therefore having to span
servers.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) = 415
We opted for partitioning data by user identifier, keep-
ing all data referring to a single user in a single database
server. This organization allows user-constrained queries
to be sent to a single database server, while requiring ge-
ographic queries to be sent to all database servers.
Data is mirrored in the system. Each database server
acts as the primary for one partition of each table, and
as the mirror (or secondary) for its neighbors’ partitions.
A primary database server processes read and write re-
quests from clients, while a mirror server only handles
read requests.
StarTrack servers are clients of the database servers,
and evenly distribute reads amongst the replicas. When
a database server fails, the server that mirrors the parti-
tions on the failed server takes over as primary for the
partitions. The StarTrack servers direct write traffic to
the new servers and in addition, distribute the read re-
quests uniformly among all the replicas using chained
declustering, as described by others [12, 19].
5 Applications
We explored scenarios where a single user’s data can be
used to personalize her experience based on her habit-
ual tracks, for applications such as personalized adver-
tising, recommendation systems, and health monitoring.
On the other end of the spectrum, social applications,
where the set of tracks from a group of friends or even a
broader community are used, may help provide enhanced
services to users. Examples include those related to ur-
ban sensing, collaboration, discovery of new areas, and
shared experiences.
To illustrate the usefulness and evaluate the perfor-
mance of StarTrack services, we describe two of the ap-
plications we built.
While both applications were non-trivial to write, the
use of our API significantly simplified their construction.
In fact, the application logic in both examples is suc-
cinctly captured in a few code snippets. Our general ex-
perience is that StarTrack provides an intuitive, flexible,
and efficient way to program track-based applications.
5.1 Ride-Sharing Service
Ride-sharing has long held the promise of reducing en-
ergy consumption. Transit departments in many major
metropolitan areas now offer on-line ride-sharing ser-
vices or portals (see for example, King County Metro
Ride [15]). One challenge in building an effective ride-
sharing service is to discover ride-share partners who
travel on similar routes.
With StarTrack, these ride-matching services are eas-
ily built. The service can build a TrackCollection for
the employees of the same company or for a person’s
416 9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
social network, or for a group of people who have sub-
scribed to a transit service. Code Segment 5.1 constructs
a track collection for a community of users. Code Seg-
ment 5.2 identifies potential ride-sharing partners based
on the similarity of their travel patterns.
Code segment 5.1 Set up a community’s regularly tra-
versed tracks where the community is defined through
supplied SearchCriteria.
TrackCollxn getCommunityTracks(SearchCriteria sc,
int count)
{
TrackCollxn tc = MakeCollection(sc, true);
return Take(SortTracks (tc, FREQ), count);
Code segment 5.2 Find ride-share candidates with sim-
ilar travel patterns. findOwners is a client-side func-
tion that takes a set of tracks and returns the list of users
who own them.
List getRideShareCandidates
(TrackCollxn communityTC, string username)
{
UserCriteria uc = new UserCriteria();
uc.Username = username;
TrackCollxn userTC =
MakeCollection(uc, true);
Track[] popularTracks =
GetTracks (SortTracks(userTC, FREQ),
0, 10);
List<TrackCollxn> similarTC;
foreach(Track track in popularTracks) {
TrackCollxn tc = GetSimilarTracks (
communityTC, track, 0.7);
similarTC.Add (tc) ;
}
Track[] similarTracks =
GetTracks (JoinTrackCollections (similarTC)
0, 100);
return findOwners (similarTracks) ;
Another usage scenario is when a user needs a ride
between two specific locations. This can be done easily
by calling GetPassbyTracks.
It is important to note that the ride-sharing service
based on StarTrack offers more flexibility than conven-
tional services. For instance, since a rider’s entire route
is known, rather than just his start and destination, it al-
lows the service more latitude in arranging pick-ups and
drop-offs along the route.
5.2 Personalized Driving Directions
Current navigation systems and online map services pro-
vide detailed turn-by-turn driving directions. Because
USENIX Association
USENIX Association
StarTrack knows what routes a person has taken in the
past, as well as how recently and how frequently, an ap-
plication could easily use StarTrack to provide personal-
ized driving directions.
For example, instead of providing detailed turn-by-
turn instructions on how to get to the freeway from the
person’s house, the directions might simply say “Get on
Highway 101 heading south” and then provide detailed
directions from that point.
Code segment 5.3 Construct a user’s familiar segments.
TrackCollxn getFamiliarSegments (string username)
{
UserCriteria uc = new UserCriteria();
uc.Username = username;
TrackCollxn uTC = MakeCollection(uc, true);
// Pick the 10 most frequently occurring
// tracks.
TrackCollxn pplrTC =
Take (SortTracks (uTC, FREQ), 10);
TrackCollxn familiarTC =
GetCommonSegments(pplrTC, 0.2);
return familiarTC;
The application we built uses the Bing Map service
and the StarTrack infrastructure. A user inputs start and
destination locations, and the application uses Bing to
get turn-by-turn directions for that route. Next, the appli-
cation uses StarTrack to obtain the set of “familiar seg-
ments” for that user, as shown in Code Segment 5.3.
Having obtained the familiar segments for the user, the
application identifies portions of the route returned by
Bing that overlap with the familiar segments and uses
the result to prepare personalized driving directions (we
omit further description of these steps given that they are
performed locally by the application and do not involve
calls to StarTrack).
6 Evaluation
This section evaluates the performance of the StarTrack
service. To study the system at scale, we used synthet-
ically generated tracks. We also ran experiments with
actual tracks collected by users of GPS-equipped mo-
bile devices, but omit the results since they are similar
to those performed with synthetic tracks, and given that
we only have a limited number of real tracks.
We focus on the costs of executing track operations
that involve (a) geographic constraints and (b) compar-
isons of tracks. These operations are the most difficult to
build efficiently, and are also among the most commonly
occurring in the track-based applications that we built.
We also report on the performance of two applications.
Our experiments were all conducted on 2.6 GHz AMD
Opteron quad-core processors with 16 GB memory, run-
ning Windows Server 2003.
6.1 Synthetic Tracks
We generated synthetic tracks based on the salient fea-
tures observed in a dataset of approximately 16,000 real
tracks followed by 252 users over 2-week periods in
Seattle, WA [16]. In our model, each person has fixed
locations for home and workplace, and a number of “er-
rand” locations that represent places they go less fre-
quently. On weekdays, a person travels between the
assigned home and work locations during the common
morning and evening commute hours. Sporadically on
weekdays and more often on weekends, a person carries
out a number of errands.
After choosing the start and end locations for each trip,
we calculate the shortest path as well as its duration be-
tween these points on a graph of road networks. We then
sample and perturb each path to simulate noise in the
sampling and localization of the data and treat the result-
ing points as a track.
Our early experiments indicated that some features of
tracks have a pronounced effect on performance while
others do not. Specifically, performance is affected by
the following:
e Number of tracks. The larger the number of tracks,
the greater the computational and storage overhead.
e Length of tracks. The number of points in a track
has an impact on performance. Assuming tracks are
canonicalized, the number of points is proportional
to the length of the tracks.
e Covered region. The region over which the tracks
are generated has an impact on track density (i.e.,
number of tracks that pass through a unit area). As
track density increases, the computational burden
imposed on our algorithms increases. For example,
the same geographic query returns more results and
therefore incurs more computational cost when the
density of tracks is higher.
We devised our model to allow us to control these key
features. Our belief is that, at least for the purpose of per-
formance evaluation, any model that allowed these fea-
tures of tracks to be varied would be adequate.
For our scalability experiments, we generated syn-
thetic tracks for a 3-month period and 18,000 users in
Santa Clara County. This resulted in a total of over 4.5
million tracks. On average, each track is 20 km long
and contains 400 GPS samples that yield on average 163
points after canonicalization.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) = 417
418
6.2 Performance of Geographic Queries
One of StarTrack’s most important operations is query-
ing based on geographic constraints. Some of these op-
erations require a round-trip to the database server, while
others can be optimized by an in-memory cache. In our
API, geographic queries show up in two forms. First, in
MakeCollection an application can specify a geographic
region constraint. Second, GetPassByTracks allows an
application to select those tracks in a track collection
that pass within specific areas. The first query involves
retrieving tracks from a database, while the second in-
volves retrieving tracks from a pre-computed track col-
lection, which can be sped up in memory.
Geographic queries to the database. Although we do
not focus on studying the performance of the spatial fea-
tures of the database, we investigate how best to use them
to improve simple geographic queries used to pre-filter
tracks brought into memory.
We compared two ways to store tracks and construct
the necessary indices. In the first approach, used in
our original prototype, we treat each track as a sepa-
rate geospatial object and create a spatial index over all
tracks. This index is used to retrieve all the tracks inter-
secting the query region. The second approach, used by
StarTrack, involves the use of two additional tables, the
Coordinate Table and the Coordinate to Track Table, as
described in Section 4.1. In this approach, a spatial index
is built only on the Coordinate Table.
T T 20
T i
original method —+—
startrack ---x---
matches ---*---
Time (s)
Number of matches (in thousands)
Square side length (km)
Figure 3: Query time with and without the Coordinate
Table when searching for tracks that intersect square re-
gions of increasing side lengths. Secondary y-axis shows
average number of tracks matched.
Figure 3 presents the query time for both approaches
when we vary the area of the query region on a set of
100,000 tracks. It also shows the average number of
matched tracks on the secondary y-axis. Isolating the
need to execute geographic queries to a small set of dis-
tinct points through the use of the Coordinate Table leads
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
Memory (MB)
Time (s)
0 ee L L L L L L L 0
20 30 40 50 60 70 80 90 100
Number of tracks (in thousands)
Figure 4: Memory usage and construction time of the
quad-tree for different sizes of tracks.
to significant performance benefits. This enhancement is
only possible due to track canonicalization.
Geographic queries to in-memory data structure. Re-
call from Section 3.2 that the evaluation of a GetPass-
ByTracks operation triggers the construction of an in-
memory quad-tree, in the expectation that the data will be
repeatedly accessed in the future. Canonicalization tends
to lower the number of unique coordinates in tracks,
speeding up the construction time for quad-trees, as well
as the execution time of subsequent requests against it.
Figure 4 shows the cost of constructing a quad-tree.
Building the quad-tree itself requires little space and time
since the number of unique coordinates is small and lev-
els off when the tracks cover a large region. Both the
memory and time needed are linear in the number of
tracks, and are mostly spent on building an index from
coordinates to their containing tracks.
Figure 5 presents the time to query a quad-tree with
varying numbers of tracks and region sizes. In all cases,
the query time is very low. For example, it takes about |
ms for a region with a 5 km radius on 100,000 tracks. The
query time is fairly insensitive to the number of tracks
because the structure of the quad-tree is determined by
the unique coordinates. On the other hand, the size of
the query region affects the times since it determines the
number of quad-tree cells to be visited.
6.3 Performance of Track Comparisons
A common query in track based applications is to retrieve
tracks based on similarity. Typically, an application has
a track collection and a “query” track and needs to find
tracks in the set that are most similar to the query track.
We compare the performance of our technique us-
ing a track tree to three alternative methods for ranking
tracks based on similarity: (1) Bruteforce: The brute-
force method compares the query track against every
track in the collection and returns those with similarity
USENIX Association
USENIX Association
radius 1km —+—" ' ,
radius 5km -=*-= nas *
125 is ee
-* aid
1 a
= Se |
@ :
é i
o «08 fF! 4
£ i
= !
> k
= 06F 4
S
o
0.4 -
0.2 + 4
0 L 1 L L L L L L L
0 10 20 30 40 50 60 70 80 90 100
Number of tracks (in thousands)
(a)
1.4 T T T T T T
tracks 50k —+—
tracks 100k ---x--- *
a
£
oO
=
=
oO
3
o
0 L L L L L L L L L
Oo o5 1 15 2 25 3 35 4 45 6
Query radius (km)
(b)
Figure 5: Time to query a quad-tree. (a) Query time for
different numbers of tracks when size of the region is
fixed to 1 and 5 km, respectively. (b) Query time for 50K
and 100K tracks as the size of region is varied.
above a given threshold. For the bruteforce method, we
assume all tracks are already in memory. (2) In-memory
filtering: This method constructs an in-memory dictio-
nary used to quickly look up tracks that contain any given
point. For a given query track, we use this dictionary to
identify all tracks that intersect it, after which we com-
pute the similarity of each intersecting track to the query
track, returning those above the threshold. (3) Database
filtering: We store the set of tracks in the database, use a
query to retrieve all tracks in the database that intersect
the query track, and compute the similarity against the
retrieved tracks.
We ran experiments with different numbers of tracks
and queries with varying similarity thresholds.
Figure 6 shows the query time when using the vari-
ous methods. The query time with the track tree method
is dependent on the similarity threshold, unlike with the
other three alternatives. In Figure 6, we present results
for the track tree approach when the similarity threshold
is 0.7 and 0.9. The experiments show that track trees lead
to significantly more efficient queries when compared to
the bruteforce method, achieving two to three orders of
magnitude speedups. Although the in-memory filtering
method performs better than the bruteforce method, it is
still significantly slower while consuming high amounts
of resources for constructing and storing the in-memory
dictionary. The database filtering method presented the
worst performance.
10000
1000 F
e 0 ke ~*°""" database filtering —+—
(OO ye bruteforce --
€ he’ in-memory filtering --
5 : seers of ssn
L" startrack 0.9 --a---J
g 10 k
Co
“0 10 20 30 40 50 60 70 80 90 100
Number of tracks (in thousands)
Figure 6: Query time comparison between StarTrack
and three alternative methods. For StarTrack, results are
shown for similarity thresholds of 0.7 and 0.9.
There is a cost associated with constructing a track tree
that is at the heart of our technique. Figure 7(a) shows
the memory usage and the time for constructing a track
tree as a function of the number of tracks in the collec-
tion. Constructing a track tree takes linear space and
slightly super-linear time as the height of the track tree
grows logarithmically with the number of tracks. There
is a tradeoff for using a track tree— it takes time to con-
struct it, but once constructed, it leads to significantly
optimized queries. From Figures 7(a) and 6, we cal-
culate the “break-even” point, or the minimum number
of queries such that the amortized query time using a
track tree is lower than the query time of the bruteforce
method. These break-even numbers are shown in Fig-
ure 7(b). As observed, the numbers grow slowly with the
number of tracks, and are fairly small: below 80 for a
track collection with up to 100,000 tracks.
One potential downside of the track tree approach is
that while it is highly efficient at retrieving similar tracks
and although it will never return tracks that do not sat-
isfy the similarity threshold, it may not return all tracks
above the given similarity threshold. Figure 7(c) shows
the coverage of the track tree method. The graph shows
the percentage of the expected tracks returned when us-
ing a track tree. We can see that the coverage increases
for higher similarity thresholds. It returns over 90% of
the tracks when similarity is above 0.7. We believe this
is sufficient for typical applications, that are only inter-
ested in tracks with reasonably high similarity.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) 419
420
80 1
180
160
140
120
100
80
60
40
20
50 F
40 |
Memory (MB)
Time (s)
30-
20
Number of break-even queries
0 20 40 60 80 100
60 F +4
1 1 100
coverage 10K tracks, —+—
coverage 50K tracks, x
coverage 100K tracks.
80
70
Coverage (%)
60
50
40 A.
30
L 1 L L L L 1 L 1
10 20 30 40 50 60 70 80 90 100 0.1
Number of tracks (in thousands) 02 03 04 05 06 07 08 O09 1
Number of tracks (in thousands) Similarity
(a) (b) (c)
Figure 7: (a) Memory and processing time required for constructing a track tree. (b) Break-even number for use of a
track tree. (c) Coverage of track tree approach as function of the similarity threshold (for 10K, 50K and 100K tracks).
6.4 Application Performance
We use the Ride-Sharing (RS) and Personalized Driving
Directions (PDD) applications, presented in Section 5,
to evaluate the overall performance of StarTrack. These
two applications illustrate two different usage scenarios:
RS creates a large track collection for repeated accesses
while PDD creates many small per-user track collections.
We fixed the number of database servers to three and
varied the number of StarTrack servers. To generate load
on the servers, we ran multiple instances of these appli-
cations from a number of client machines.
6.4.1 Single StarTrack Server Experiments
The RS application identifies potential ride-sharing part-
ners for a given user, and as presented in Code Seg-
ment 5.2, involves multiple calls to the StarTrack server.
In our evaluation, we built a track collection with 50,000
unique tracks from which the application searches for
similar tracks. We warmed up the server by construct-
ing a track tree on the large set of tracks before sending
it client requests. Figure 8(a) shows the response times
for RS under varying request rates. Despite the more
complex nature of the application, one StarTrack server
is capable of satisfying 30 requests per second with a re-
sponse rate of around 150 ms.
We ran experiments for the PDD application under two
different types of load. In the first case, queries simulate
users whose data has not been cached on the StarTrack
server prior to the query. In the second case, we preload
the cache with the in-memory data structures used to ex-
pedite the GetCommonSegments operation (familiarTC
in Code Segment 5.3) invoked by the application.
Figures 8(b) and (c) plot the response times with vary-
ing request rates under the two types of loads. When
the data is not cached, each server is capable of satisfy-
ing up to 30 requests per second without increasing the
response time. The average response time prior to satura-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
tion is around 100 ms. The maximum server throughput
increases to 270 requests per second and the response
time falls to 60 ms when the data is previously cached on
the server.
6.4.2 Scalability Experiments
For both applications, individual requests sent by the
clients are entirely independent of one another. We tested
StarTrack’s scalability by running the PDD application
on multiple StarTrack servers. For this experiment we
used the non-cached version of PDD, with the goal of
exercising load on the database.
In Figure 9 we present the maximum throughput that
the system is able to achieve with a varying number of
StarTrack servers. As expected, the system scales lin-
early with the number of servers. Since PDD only re-
trieves a small number of tracks for each user, this exper-
iment did not saturate the database servers.
From these experiments, we estimate the resources
needed to satisfy a given number of users for our tested
applications. Three StarTrack servers can support a peak
load of around 120 requests per second (without caching)
or up to 780 (with caching). Without caching, this allows
over 5 million queries uniformly distributed over a period
of 12 hours, corresponding to an average of 5 queries per
user given a population of 1 million users requesting per-
sonalized driving directions.
In the case of ride-sharing, it’s desirable that track
trees are pre-built and kept in memory. In order to cre-
ate and cache a single or multiple track trees with each
user’s top 5 tracks, a ride-sharing application satisfying
1 million users would require approximately 10 GB of
memory. A single server holding all this data could allow
a peak load of 35 requests per second, or more servers
could be used if higher peak loads need to be handled.
USENIX Association
USENIX Association
550
120 r T 1 1
4 110 5 4
4 100 - 4
90 + 4
80 + 4
70+ 4
60 + 4
Response time (ms)
al 50 + 4
4 40 b 4
L L L r 30 L L L L L L
500 1
500 + 4 450 +
— 450+ 4 — 400+
@ B
E 400} 4 E 350}
oO oO
— 350+ 4 £ 300}
o oO
2 300+ 4 2 250+
°° 3°
& 250+ + & 200-
oD ®
ac c
200 + 4 150 +
150 + 4 100
100 ! ! ! \ L L L 50 Lo
0 5 10 15 20 25 30 35 40 5 10
Request rate per second
(a)
15 20 25 30 35 40 45 50
Request rate per second
(b)
140 160 180 200 220 240 260
Request rate per second
(c)
280
Figure 8: Response times for the RS and PDD applications under varying request rates. (a) RS application; (b) PDD
where users’ tracks are not cached; (c) PDD where users’ tracks are previously cached.
300 ial T T T
maximum request rate x ae"
trendline
250 +
150 -
100 + a
50."
Maximum request rate per second
0 Ll Ll 1 Ll L l l
1 2 3 4 5 6 7 8 9
Number of StarTrack servers
Figure 9: Maximum aggregate request rate with increas-
ing numbers of StarTrack servers.
7 Related Work
As mobile devices have become equipped with the abil-
ity to determine their own location, there has been an
emergence of applications that collect and utilize users’
location data. The research community has proposed
a number of useful location-based applications. Traffic
prediction [11, 24], ride-sharing [14], personalized driv-
ing directions [21] and electronic tour guides [1, 25] are
some compelling examples.
At present, every application is forced to maintain its
own silo of user location data. StarTrack addresses this
problem by providing a common infrastructure that col-
lects location information and enables access to it by
multiple applications. In recent years, a number of data
platforms (such as Twitter and Facebook) have emerged
that enable sharing of information between users. These
platforms provide external application developers with
an API for accessing user information. StarTrack can be
thought of as a platform that stores and enables access to
the tracks traversed by users in their daily lives.
Efficient collection of location data is an important
precursor to organizing this data and making it acces-
sible. The CarTel project [13] is a distributed sensor net-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
work that supports data collection from mobile phones
and vehicular sensor networks. CarTel allows applica-
tions to visualize traces stored in a relational database
using spatial queries.
Database researchers have extensively studied the
problem of storing, indexing, and retrieving trajectories.
A trajectory is similar to a track in our system and is
modeled as a geometric object with 3 dimensions: two
for geographical location and a third for time. Prior work
has focused on range queries on trajectories and has led
to novel indexing techniques. For example, research has
shown that it is more efficient to separate the spatial and
temporal dimensions and to first index the spatial dimen-
sions [6]. There is also research that optimizes storage
and query costs when trajectories are drawn from a fixed
road network [2, 4, 22]. Some of the design decisions in
StarTrack are based on similar observations. StarTrack
additionally allows tracks with very similar geometries
to be pruned, resulting in even greater savings. Fur-
thermore, StarTrack exploits the repetitiveness in users’
tracks drawn from a road map to implement efficient sim-
ilarity and common segment queries, which are not stud-
ied in previous work.
8 Conclusion
StarTrack enables a broad class of track-based applica-
tions, involving both individual users and social network-
ing groups. Our original design of the StarTrack platform
focused almost exclusively on the set of operations that
would be useful to application developers and ignored
performance and scalability considerations. Significant
work went into revising the StarTrack design and imple-
mentation to enhance its efficiency, robustness, scalabil-
ity, and ease of use. In some cases, we were able to apply
well-known techniques, such as vertical data partitioning
and chained declustering. However, most of the observed
improvements come from innovative data structures like
track trees, new representations for canonicalized tracks,
421
422
and novel uses of delayed execution and caching.
The end result is a track-based service that shows sev-
eral orders of magnitude improvement in performance
for operations that are commonly used in the applications
that we have developed. This allows such applications
to meet their scalability requirements. Moving forward,
we plan to build and deploy additional track-based ap-
plications to further validate the practical utility of our
redesigned service.
Acknowledgments
We thank our former interns Ganesh Ananthanarayanan
and Erich Stuntebeck for their work on earlier versions
of the system. We thank Lenin Ravindranath and Re-
nato Werneck for helpful discussions; John Krumm, Paul
Newson, and Eric Horvitz for providing us with user lo-
cation data and the map matching software; and Daniel
Delling for the shortest path software used to generate
synthetic tracks. The anonymous referees and our shep-
herd, Brad Karp, provided useful suggestions for im-
proving the paper.
References
[1] ABowb, G. D., ATKESON, C. G., HONG, J., LONG, S.,
KOOPER, R., AND PINKERTON, M. Cyberguide: A mobile
context-aware tour guide. Wirel. Netw. 3, 5 (1997), 421-433.
[2] ALMEIDA, V. T. D., AND GUTING, R. H. Indexing the trajecto-
ries of moving objects in networks. Geoinformatica 9, 1 (2005),
33-60.
[3] ANANTHANARAYANAN, G., HARIDASAN, M., MOHOMED, I.,
TERRY, D., AND THEKKATH, C. A. StarTrack: A framework for
enabling track-based applications. In MobiSys ’09: Proceedings
of the 7th International Conference on Mobile Systems, Applica-
tions, and Services (2009), pp. 207-220.
[4] BRAKATSOULAS, S., PFOSER, D., AND TRYFONA, N. Practical
data management techniques for vehicle tracking data. In JCDE
’05: Proceedings of the 21st International Conference on Data
Engineering (2005), pp. 324-325.
[5] Cao, L., AND KRUMM, J. From GPS traces to a routable road
map. In GIS ’09: Proceedings of 17th ACM SIGSPATIAL In-
ternational Symposium on Advances in Geographic Information
Systems (2009), pp. 3-12.
[6] CHAKKA, V. P., EVERSPAUGH, A., AND PATEL, J. M. Indexing
large trajectory data sets with SETI. In CIDR ’03: Ist Conference
on Innovative Data Systems Research (2003).
[7] CHEN, D., GUIBAS, L. J., HERSHBERGER, J., AND SUN, J.
Road network reconstruction for organizing paths. In SODA ’10:
Proceedings of 21st ACM-SIAM Symposium on Discrete Algo-
rithms (2010), pp. 1309-1320.
[8] CHENG, Y.-C., CHAWATHE, Y., LAMARCA, A., AND KRUMM,
J. Accuracy characterization for metropolitan-scale wi-fi lo-
calization. In MobiSys ’05: Proceedings of the 3rd Interna-
tional Conference on Mobile Systems, Applications, and Services
(2005), pp. 233-245.
[9] DAYAL, U., AND BERNSTEIN, P. A. On the updatability of re-
lational views. In VLDB ’78: Proceedings of the 4th Interna-
tional Conference on Very Large Data Bases - Volume 4 (1978),
pp. 368-377.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
[10]
(11)
[12]
[13]
[14]
[15]
[16]
[17]
[18]
[19]
[20]
[21]
[22]
[23]
[24]
[25]
FINKEL, R. A., AND BENTLEY, J. L. Quad trees: A data struc-
ture for retrieval on composite keys. Acta Informatica 4, 1 (2004),
1-9.
HoRVITZ, E., APACIBLE, J., SARIN, R., AND LIAO, L. Predic-
tion, expectation, and surprise: Methods, designs, and study of
a deployed traffic forecasting service. In UAI ’05: Proceedings
of the 21st Conference on Uncertainty in Artificial Intelligence
(2005), pp. 433-437.
HsIAO, H.-I., AND DEWITT, D. J. Chained declustering: A
new availability strategy for multiprocessor database machines.
In ICDE ’90: Proceedings of the 6th International Conference
on Data Engineering (1990), pp. 456-465.
HULL, B., BYCHKOVSKY, V., ZHANG, Y., CHEN, K.,
GORACZKO, M., MIUu, A. K., SHIH, E., BALAKRISHNAN, H.,
AND MADDEN, S. CarTel: A distributed mobile sensor comput-
ing system. In SenSys ’06: Proceedings of the 4th ACM Confer-
ence on Embedded Networked Sensor Systems (2006), pp. 125-
138.
KAMAR, E., AND HORVITZ, E. Collaboration and shared plans
in the open world: Studies of ridesharing. In IJCAI’09: Pro-
ceedings of the 21st International Joint Conference on Artifical
intelligence (2009), p. 187.
King county metro _ transit: Rideshare online
http://www.rideshareonline.com, 2010.
KRUMM, J., AND HORVITZ, E. The Microsoft multiperson loca-
tion survey. Tech. Rep. MSR-TR-2005-103, Microsoft Research,
Redmond, WA, USA, 2005.
KRUMM, J., LETCHNER, J.,. AND HORVITZ, E. Map match-
ing with travel time constraints. In SAE ’07: Proceedings of the
Society of Automotive Engineers World Congress (2007).
LANDIN, P. J. The mechanical evaluation of expressions. Com-
puter Journal 6 (January 1964), 308-320.
LEE, E. K., AND THEKKATH, C. A. Petal: Distributed virtual
disks. In ASPLOS ’96: Proceedings of the 7th International Con-
ference on Architectural Support for Programming Languages
and Operating Systems (1996), pp. 84-92.
NEWSON, P., AND KRUMM, J. Hidden Markov map matching
through noise and sparseness. In GIS ’09: Proceedings of the
17th ACM SIGSPATIAL International Conference on Advances
in Geographic Information Systems (2009), pp. 336-343.
PATEL, K., CHEN, M. Y., SMITH, I., AND LANDAY, J. A. Per-
sonalizing routes. In UIST ’06: Proceedings of the 19th An-
nual ACM Symposium on User Interface Software and Technol-
ogy (2006), pp. 187-190.
PFOSER, D., AND JENSEN, C. S. Indexing of network con-
strained moving objects. In GIS ’03: Proceedings of the 11th
ACM SIGSPATIAL International Symposium on Advances in Ge-
ographic Information Systems (2003), pp. 25-32.
SONG, C., QU, Z., BLUMM, N., AND BARABSI, A.-L. Limits
of predictability in human mobility. Science 327, 5968 (2010),
1018-1021.
THIAGARAJAN, A., RAVINDRANATH, L., LACURTS, K.,
MADDEN, S., BALAKRISHNAN, H., TOLEDO, S., AND ERIKS-
SON, J. VTrack: Accurate, energy-aware road traffic delay esti-
mation using mobile phones. In SenSys ’09: Proceedings of the
7th ACM Conference on Embedded Networked Sensor Systems
(2009), pp. 85-98.
ZHENG, Y., WANG, L., ZHANG, R., XIE, X., AND MA, W.-Y.
GeoLife: Managing and understanding your past life over maps.
In MDM ’08: Proceedings of the 9th International Conference on
Mobile Data Management (2008), pp. 357-358.
USENIX Association
USENIX Association
The Turtles Project: Design and Implementation of Nested Virtualization
Muli Ben-Yehudat
muli @il.ibm.com
Michael D. Day?
mdday @us.ibm.com
Abel Gordon’ Anthony Liguori?
abelg @il.ibm.com aliguori@us.ibm.com
'IBM Research — Haifa
Abstract
In classical machine virtualization, a hypervisor runs
multiple operating systems simultaneously, each on its
own virtual machine. In nested virtualization, a hypervi-
sor can run multiple other hypervisors with their associ-
ated virtual machines. As operating systems gain hyper-
visor functionality—Microsoft Windows 7 already runs
Windows XP in a virtual machine—nested virtualization
will become necessary in hypervisors that wish to host
them. We present the design, implementation, analysis,
and evaluation of high-performance nested virtualization
on Intel x86-based systems. The Turtles project, which
is part of the Linux/KVM hypervisor, runs multiple un-
modified hypervisors (e.g., KVM and VMware) and op-
erating systems (e.g., Linux and Windows). Despite the
lack of architectural support for nested virtualization in
the x86 architecture, it can achieve performance that is
within 6-8% of single-level (non-nested) virtualization
for common workloads, through multi-dimensional pag-
ing for MMU virtualization and multi-level device as-
signment for I/O virtualization.
The scientist gave a superior smile before re-
plying, “What is the tortoise standing on?”
“You’re very clever, young man, very clever”,
said the old lady. “But it’s turtles all the way
down!”!
1 Introduction
Commodity operating systems increasingly make use
of virtualization capabilities in the hardware on which
they run. Microsoft’s newest operating system, Win-
dows 7, supports a backward compatible Windows XP
mode by running the XP operating system as a virtual
machine. Linux has built-in hypervisor functionality
‘http://en.wikipedia.org/wiki/Turtles_all_the_way_.down
Zvi Dubitzky"
dubi @il.ibm.com
Orit Wassermant
oritw @il.ibm.com
*IBM Linux Technology Center
Nadav Har’ El"
nyh@il.ibm.com
Michael Factort
factor @il.ibm.com
Ben-Ami Yassourt
benami @il.ibm.com
via the KVM [29] hypervisor. As commodity operat-
ing systems gain virtualization functionality, nested vir-
tualization will be required to run those operating sys-
tems/hypervisors themselves as virtual machines.
Nested virtualization has many other potential uses.
Platforms with hypervisors embedded in firmware [1,20]
need to support any workload and specifically other hy-
pervisors as guest virtual machines. An Infrastructure-
as-a-Service (IaaS) provider could give a user the ability
to run a user-controlled hypervisor as a virtual machine.
This way the cloud user could manage his own virtual
machines directly with his favorite hypervisor of choice,
and the cloud provider could attract users who would like
to run their own hypervisors. Nested virtualization could
also enable the live migration [14] of hypervisors and
their guest virtual machines as a single entity for any
reason, such as load balancing or disaster recovery. It
also enables new approaches to computer security, such
as honeypots capable of running hypervisor-level root-
kits [43], hypervisor-level rootkit protection [39,44], and
hypervisor-level intrusion detection [18, 25]—for both
hypervisors and operating systems. Finally, it could also
be used for testing, demonstrating, benchmarking and
debugging hypervisors and virtualization setups.
The anticipated inclusion of nested virtualization in
x86 operating systems and hypervisors raises many in-
teresting questions, but chief amongst them is its runtime
performance cost. Can it be made efficient enough that
the overhead doesn’t matter? We show that despite the
lack of architectural support for nested virtualization in
the x86 architecture, efficient nested x86 virtualization—
with as little as 6-8% overhead—is feasible even when
running unmodified binary-only hypervisors executing
non-trivial workloads.
Because of the lack of architectural support for nested
virtualization, an x86 guest hypervisor cannot use the
hardware virtualization support directly to run its own
guests. Fundamentally, our approach for nested virtual-
ization multiplexes multiple levels of virtualization (mul-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) = 423
424
tiple hypervisors) on the single level of architectural sup-
port available. We address each of the following ar-
eas: CPU (e.g., instruction-set) virtualization, memory
(MMU) virtualization, and I/O virtualization.
x86 virtualization follows the “trap and emulate”
model [21,22,36]. Since every trap by a guest hypervisor
or operating system results in a trap to the lowest (most
privileged) hypervisor, our approach for CPU virtualiza-
tion works by having the lowest hypervisor inspect the
trap and forward it to the hypervisors above it for emula-
tion. We implement a number of optimizations to make
world switches between different levels of the virtualiza-
tion stack more efficient. For efficient memory virtual-
ization, we developed multi-dimensional paging, which
collapses the different memory translation tables into the
one or two tables provided by the MMU [13]. For effi-
cient I/O virtualization, we bypass multiple levels of hy-
pervisor I/O stacks to provide nested guests with direct
assignment of I/O devices [11, 31, 37,52, 53] via multi-
level device assignment.
Our main contributions in this work are:
e The design and implementation of nested virtual-
ization for Intel x86-based systems. This imple-
mentation can run unmodified hypervisors such as
KVM and VMware as guest hypervisors, and can
run multiple operating systems such as Linux and
Windows as nested virtual machines. Using mullti-
dimensional paging and multi-level device assign-
ment, it can run common workloads with overhead
as low as 6-8% of single-level virtualization.
The first evaluation and analysis of nested x86 virtu-
alization performance, identifying the main causes
of the virtualization overhead, and classifying them
into guest hypervisor issues and limitations in the
architectural virtualization support. We also sug-
gest architectural and software-only changes which
could reduce the overhead of nested x86 virtualiza-
tion even further.
2 Related Work
Nested virtualization was first mentioned and theoreti-
cally analyzed by Popek and Goldberg [21, 22,36]. Bel-
paire and Hsu extended this analysis and created a formal
model [10]. Lauer and Wyeth [30] removed the need for
a central supervisor and based nested virtualization on
the ability to create nested virtual memories. Their im-
plementation required hardware mechanisms and corre-
sponding software support, which bear little resemblance
to today’s x86 architecture and operating systems.
Belpaire and Hsu also presented an alternative ap-
proach for nested virtualization [9]. In contrast to today’s
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
x86 architecture which has a single level of architectural
support for virtualization, they proposed a hardware ar-
chitecture with multiple virtualization levels.
The IBM z/VM hypervisor [35] included the first prac-
tical implementation of nested virtualization, by making
use of multiple levels of architectural support. Nested
virtualization was also implemented by Ford et al. in a
microkernel setting [16] by modifying the software stack
at all levels. Their goal was to enhance OS modularity,
flexibility, and extensibility, rather than run unmodified
hypervisors and their guests.
During the last decade software virtualization tech-
nologies for x86 systems rapidly emerged and were
widely adopted by the market, causing both AMD and
Intel to add virtualization extensions to their x86 plat-
forms (AMD SVM [4] and Intel VMX [48]). KVM [29]
was the first x86 hypervisor to support nested virtualiza-
tion. Concurrent with this work, Alexander Graf and Jo-
erg Roedel implemented nested support for AMD pro-
cessors in KVM [23]. Despite the differences between
VMX and SVM—VMxX takes approximately twice as
many lines of code to implement—nested SVM shares
many of the same underlying principles as the Turtles
project. Multi-dimensional paging was also added to
nested SVM based on our work, but multi-level device
assignment is not implemented.
There was also a recent effort to incorporate nested
virtualization into the Xen hypervisor [24], which again
appears to share many of the same underlying principles
as our work. It is, however, at an early stage: it can only
run a single nested guest on a single CPU, does not have
multi-dimensional paging or multi-level device assign-
ment, and no performance results have been published.
Blue Pill [43] is a root-kit based on hardware virtual-
ization extensions. It is loaded during boot time by in-
fecting the disk master boot record. It emulates VMX
in order to remain functional and avoid detection when a
hypervisor is installed in the system. Blue Pill’s nested
virtualization support is minimal since it only needs to
remain undetectable [17]. In contrast, a hypervisor with
nested virtualization support must efficiently multiplex
the hardware across multiple levels of virtualization deal-
ing with all of CPU, MMU, and I/O issues. Unfortu-
nately, according to its creators, Blue Pill’s nested VMX
implementation can not be published.
ScaleMP vSMP is acommercial product which aggre-
gates multiple x86 systems into a single SMP virtual ma-
chine. ScaleMP recently announced a new “VM on VM”
feature which allows running a hypervisor on top of their
underlying hypervisor. No details have been published
on the implementation.
Berghmans demonstrates another approach to nested
x86 virtualization, where a software-only hypervisor is
run on a hardware-assisted hypervisor [12]. In contrast,
USENIX Association
USENIX Association
our approach allows both hypervisors to take advantage
of the virtualization hardware, leading to a more efficient
implementation.
3 Turtles: Design and Implementation
The IBM Turtles nested virtualization project imple-
ments nested virtualization for Intel’s virtualization tech-
nology based on the KVM [29] hypervisor. It can host
multiple guest hypervisors simultaneously, each with its
own multiple nested guest operating systems. We have
tested it with unmodified KVM and VMware Server as
guest hypervisors, and unmodified Linux and Windows
as nested guest virtual machines. Since we treat nested
hypervisors and virtual machines as unmodified black
boxes, the Turtles project should also run any other x86
hypervisor and operating system.
The Turtles project is fairly mature: it has been tested
running multiple hypervisors simultaneously, supports
SMP, and takes advantage of two-dimensional page table
hardware where available in order to implement nested
MMU virtualization via multi-dimensional paging. It
also makes use of multi-level device assignment for effi-
cient nested I/O virtualization.
3.1 Theory of Operation
There are two possible models for nested virtualization,
which differ in the amount of support provided by the
underlying architecture. In the first model, multi-level
architectural support for nested virtualization, each hy-
pervisor handles all traps caused by sensitive instructions
of any guest hypervisor running directly on top of it. This
model is implemented for example in the IBM System z
architecture [35].
The second model, single-level architectural support
for nested virtualization, has only a single hypervisor
mode, and a trap at any nesting level is handled by this
hypervisor. As illustrated in Figure 1, regardless of the
level in which a trap occurred, execution returns to the
level 0 trap handler. Therefore, any trap occurring at
any level from 1... causes execution to drop to level
0. This limited model is implemented by both Intel and
AMD in their respective x86 virtualization extensions,
VMX [48] and SVM [4].
Since the Intel x86 architecture is a single-level vir-
tualization architecture, only a single hypervisor can
use the processor’s VMX instructions to run its guests.
For unmodified guest hypervisors to use VMX instruc-
tions, this single bare-metal hypervisor, which we call
Lo, needs to emulate VMX. This emulation of VMX can
work recursively. Given that Lo provides a faithful em-
ulation of the VMX hardware any time there is a trap
on VMxX instructions, the guest running on L; will not
i t
Single Two Levels
Level
Three Levels
Figure 1: Nested traps with single-level architectural
support for virtualization
know it is not running directly on the hardware. Build-
ing on this infrastructure, the guest at L, is itself able
use the same techniques to emulate the VMX hardware
to an Lz hypervisor which can then run its L3 guests.
More generally, given that the guest at L,_1 provides a
faithful emulation of VMX to guests at L,,, a guest at L,
can use the exact same techniques to emulate VMX for a
guest at L,,,1. We thus limit our discussion below to Lo,
Li, and Lo.
Fundamentally, our approach for nested virtualization
works by multiplexing multiple levels of virtualization
(multiple hypervisors) on the single level of architectural
support for virtualization, as can be seen in Figure 2.
Traps are forwarded by Lo between the different levels.
ooss---f--------- a
| Guest Guest |!
i} L2 L2
fut Guest ‘ffs Guest |] Guest] |Guest
! Hypervisor a ‘| Hypervisor feeles| Guest
Lo Host Hypervisor [to] Host Hypervisor
Multiple levels support
Multiplexing a single level
Figure 2: Multiplexing multiple levels of virtualization
on a single hardware-provided level of support
When L, wishes to run a virtual machine, it launches it
via the standard architectural mechanism. This causes a
trap, since Lj is not running in the highest privilege level
(as is Lo). To run the virtual machine, L; supplies a spec-
ification of the virtual machine to be launched, which
includes properties such as its initial instruction pointer
and its page table root. This specification must be trans-
lated by Lo into a specification that can be used to run
L» directly on the bare metal, e.g., by converting mem-
ory addresses from L,’s physical address space to Lo’s
physical address space. Thus Lo multiplexes the hard-
ware between L, and Lg, both of which end up running
as Lo virtual machines.
When any hypervisor or virtual machine causes a trap,
the Lo trap handler is called. The trap handler then in-
spects the trapping instruction and its context, and de-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) = 425
426
cides whether that trap should be handled by Lo (e.g.,
because the trapping context was L,) or whether to for-
ward it to the responsible hypervisor (e.g., because the
trap occurred in Lz and should be handled by L). In the
latter case, Lo forwards the trap to L; for handling.
When there are n levels of nesting guests, but the hard-
ware supports less than n levels of MMU or DMA trans-
lation tables, the n levels need to be compressed onto the
levels available in hardware, as described in Sections 3.3
and 3.4.
3.2 CPU: Nested VMX Virtualization
Virtualizing the x86 platform used to be complex and
slow [40, 41,49]. The hypervisor was forced to re-
sort to on-the-fly binary translation of privileged instruc-
tions [3], slow machine emulation [8], or changes to
guest operating systems at the source code level [6] or
during compilation [32].
In due time Intel and AMD incorporated hardware
virtualization extensions in their CPUs. These exten-
sions introduced two new modes of operation: root mode
and guest mode, enabling the CPU to differentiate be-
tween running a virtual machine (guest mode) and run-
ning the hypervisor (root mode). Both Intel and AMD
also added special in-memory virtual machine control
structures (VMCS and VMCB, respectively) which con-
tain environment specifications for virtual machines and
the hypervisor.
The VMxX< instruction set and the VMCS layout are ex-
plained in detail in [27]. Data stored in the VMCS can be
divided into three groups. Guest state holds virtualized
CPU registers (e.g., control registers or segment regis-
ters) which are automatically loaded by the CPU when
switching from root mode to guest mode on VMEntry.
Host state is used by the CPU to restore register val-
ues when switching back from guest mode to root mode
on VMExit. Control data is used by the hypervisor to
inject events such as exceptions or interrupts into vir-
tual machines and to specify which events should cause
a VMExit; it is also used by the CPU to specify the
VMExit reason to the hypervisor.
In nested virtualization, the hypervisor running in root
mode (Lo) runs other hypervisors (L;) in guest mode.
L, hypervisors have the illusion they are running in root
mode. Their virtual machines (L2) also run in guest
mode.
As can be seen in Figure 3, Lg is responsible for mul-
tiplexing the hardware between L; and Lz. The CPU
runs L; using VMCSo-_,1 environment specification. Re-
spectively, VMCSo_+2 is used to run Ly. Both of these
environment specifications are maintained by Lo. In ad-
dition, L; creates VMCS,_.2 within its own virtualized
environment. Although VMCS,_,2 is never loaded into
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
the processor, Lo uses it to emulate a VMX enabled CPU
for Lj.
1 L2 |Guest
Guest OS
Hypervisor ;
vwes— Memory N15 state
Tables_}
Memo Memory
0-1 State [| VMCS Table VMCS Tables 0-2 State
Lo Host Hypervisor
Figure 3: Extending VMX for nested virtualization
3.2.1. VMX Trap and Emulate
VMxX instructions can only execute successfully in root
mode. In the nested case, L; uses VMX instructions in
guest mode to load and launch L2 guests, which causes
VMExits. This enables Lo, running in root mode, to trap
and emulate the VMX instructions executed by Lj.
In general, when Lo emulates VMX instructions, it
updates VMCS structures according to the update pro-
cess described in the next section. Then, Lg resumes
Lj, as though the instructions were executed directly by
the CPU. Most of the VMX instructions executed by L;
cause, first, a VMExit from L, to Lo, and then a VMEn-
try from Lo to Li.
For the instructions used to run a new VM,
vmresume and vmlaunch, the process is different,
since Lo needs to emulate a VMEntry from L; to Lo.
Therefore, any execution of these instructions by L,
cause, first, a VMExit from L; to Lo, and then, a VMEn-
try from Lo to Lo.
3.2.2 VMCS Shadowing
Lo prepares a VMCS (VMCSo-,1) to run Li, exactly in
the same way a hypervisor executes a guest with a single
level of virtualization. From the hardware’s perspective,
the processor is running a single hypervisor (Lo) in root
mode and a guest (L;) in guest mode. L, is not aware
that it is running in guest mode and uses VMX instruc-
tions to create the specifications for its own guest, Le.
L, defines L2’s environment by creating a VMCS
(VMCS,_,2) which contains Ly’s environment from L,’s
perspective. For example, the VMCSj_.2 GUEST-CR3
field points to the page tables that L; prepared for Lo.
Lo cannot use VMCSj-_,2 to execute Lg directly, since
VMCSj-_,9 is not valid in Lo’s environment and Lo can-
not use L,’s page tables to run Ly. Instead, Lo uses
USENIX Association
USENIX Association
VMCS}j_,2 to construct a new VMCS (VMCSo-_,2) that
holds L2’s environment from Lo’s perspective.
Lo must consider all the specifications defined
in VMCS,_,29 and also the specifications defined in
VMCSo_,1 to create VMCSo_,2. The host state defined in
VMCSo-_,2 must contain the values required by the CPU
to correctly switch back from Lz to Lo. In addition,
VMCS}-,2 host state must be copied to VMCSo-_,1 guest
state. Thus, when Lp emulates a switch between L» to
L;, the processor loads the correct L, specifications.
The guest state stored in VMCS1_,2 does not require
any special handling in general, and most fields can be
copied directly to the guest state of VMCSo_,2.
The control data of VMCS;_,2 and VMCSo_,; must be
merged to correctly emulate the processor behavior. For
example, consider the case where L specifies to trap an
event F’'4 in VMCSj_,2 but Lo does not trap such event
for L; (i.e., a trap is not specified in VMCSo-_,1). To for-
ward the event FE’, to L;, Lo needs to specify the corre-
sponding trap in VMCSo-.2. In addition, the field used by
L; to inject events to Lz needs to be merged, as well as
the fields used by the processor to specify the exit cause.
For the sake of brevity, we omit some details on how
specific VMCS fields are merged. For the complete de-
tails, the interested reader is encouraged to refer to the
KVM source code [29].
3.2.3 VMEntry and VMExit Emulation
In nested environments, switches from L, to Lz and back
must be emulated. When Lg is running and a VMExit
occurs there are two possible handling paths, depending
on whether the VMExit must be handled only by Lo or
must be forwarded to L.
When the event causing the VMExit is related to Lo
only, Lo handles the event and resumes L2. This kind of
event can be an external interrupt, a non-maskable inter-
rupt (NMJ) or any trappable event specified in VMCSo_,2
that was not specified in VMCSj_,2. From L,’s perspec-
tive this event does not exist because it was generated
outside the scope of L,’s virtualized environment. By
analogy to the non-nested scenario, an event occurred at
the hardware level, the CPU transparently handled it, and
the hypervisor continued running as before.
The second handling path is caused by events related
to L; (e.g., trappable events specified in VMCSj-_,2).
In this case Lo forwards the event to L; by copying
VMCSp-.2 fields updated by the processor to VMCSj_,2
and resuming L;. The hypervisor running in L, believes
there was a VMExit directly from L2 to L;. The L; hy-
pervisor handles the event and later on resumes Lz by
executing vmresume or vmlaunch, both of which will
be emulated by Lo.
3.3. MMU: Multi-dimensional Paging
In addition to virtualizing the CPU, a hypervisor also
needs to virtualize the MMU: A guest OS builds a guest
page table which translates guest virtual addresses to
guest physical addresses. These must be translated again
into host physical addresses. With nested virtualization,
a third layer of address translation is needed.
These translations can be done entirely in software,
or assisted by hardware. However, as we explain be-
low, current hardware supports only one or two dimen-
sions (levels) of translation, not the three needed for
nested virtualization. In this section we present a new
technique, multi-dimensional paging, for multiplexing
the three needed translation tables onto the two avail-
able in hardware. In Section 4.1.2 we demonstrate the
importance of this technique, showing that more naive
approaches (surveyed below) cause at least a three-fold
slowdown of some useful workloads.
When no hardware support for memory manage-
ment virtualization was available, a technique known as
shadow page tables [15] was used. A guest creates a
guest page table, which translates guest virtual addresses
to guest physical addresses. Based on this table, the hy-
pervisor creates a new page table, the shadow page ta-
ble, which translates guest virtual addresses directly to
the corresponding host physical address [3,6]. The hy-
pervisor then runs the guest using this shadow page table
instead of the guest’s page table. The hypervisor has to
trap all guest paging changes, including page fault excep-
tions, the INVLPG instruction, context switches (which
cause the use of a different page table) and all the guest
updates to the page table.
To improve virtualization performance, x86 architec-
tures recently added two-dimensional page tables [13]—
a second translation table in the hardware MMU. When
translating a guest virtual address, the processor first uses
the regular guest page table to translate it to a guest phys-
ical address. It then uses the second table, called EPT by
Intel (and NPT by AMD), to translate the guest physi-
cal address to a host physical address. When an entry
is missing in the EPT table, the processor generates an
EPT violation exception. The hypervisor is responsible
for maintaining the EPT table and its cache (which can
be flushed with INVEPT), and for handling EPT viola-
tions, while guest page faults can be handled entirely by
the guest.
The hypervisor, depending on the processors capabil-
ities, decides whether to use shadow page tables or two-
dimensional page tables to virtualize the MMU. In nested
environments, both hypervisors, Lo and L,, determine
independently the preferred mechanism. Thus, Lo and
L, hypervisors can use the same or a different MMU
virtualization mechanism. Figure 4 shows three differ-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) = 427
428
ent nested MMU virtualization models.
“| Ly virtual -| L, virtual L, virtual
ow Z v e 2 I~
GPT GPT GPT
¥ » L, physical “> L, physical oe y L, physical
SPT12 SPTO2 EPT12
~~~») L, physical L, physical v “> L, physical EpTo2
EPTO1 | EPTO1 ce
Ly physical Ly physical «—~ ~~-» Lo physical
1. Shadow page tables
on top of shadow page
tables
2. Shadow page tables
on top of EPT
3. multi-dimensional paging
(EPT on top of EPT)
Figure 4: MMU alternatives for nested virtualization
Shadow-on-shadow is used when the processor does
not support two-dimensional page tables, and is the least
efficient method. Initially, Lo creates a shadow page ta-
ble to run L; (SPTo_.1). Ly, in turn, creates a shadow
page table to run L2 (SPT1_,2). Lo cannot use SPT;_.2
to run Lz because this table translates Lz guest virtual
addresses to L; host physical addresses. Therefore, Lo
compresses SPTo_.1 and SPT,_,2 into a single shadow
page table, SPTp_,2. This new table translates directly
from Lg guest virtual addresses to Lo host physical ad-
dresses. Specifically, for each guest virtual address in
SPT1_.2, Lo creates an entry in SPTp_,2 with the corre-
sponding Lo host physical address.
Shadow-on-EPT is the most straightforward approach
to use when the processor supports EPT. Lo uses the EPT
hardware, but L; cannot use it, so it resorts to shadow
page tables. L; uses SPT;_,2 to run Lz. Lo configures
the MMU to use SPT _.2 as the first translation table and
EPTg_1 as the second translation table. In this way, the
processor first translates from L2 guest virtual address to
L, host physical address using SPT,_,2, and then trans-
lates from the L; host physical address to the Lo host
physical address using the EPTo_,1.
Though the Shadow-on-EPT approach uses the EPT
hardware, it still has a noticeable overhead due to page
faults and page table modifications in Lz. These must
be handled in Lj, to maintain the shadow page ta-
ble. Each of these faults and writes cause VMExits
and must be forwarded from Lo to L; for handling. In
other words, Shadow-on-EPT is slow for the exactly the
same reasons that Shadow itself was slow for single-level
virtualization—but it is even slower because nested exits
are slower than non-nested exits.
In multi-dimensional page tables, as in two-
dimensional page tables, each level creates its own sepa-
rate translation table. For L; to create an EPT table, Lo
exposes EPT capabilities to L;, even though the hard-
ware only provides a single EPT table.
Since only one EPT table is available in hardware, the
two EPT tables should be compressed into one: Let us
assume that Lo runs L; using EPTo_.1, and that Lj cre-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
ates an additional table, EPT;_,2, to run L2, because Lo
exposed a virtualized EPT capability to L;. The Lo hy-
pervisor could then compress EPTo_,; and EPT,_,2 into
a single EPTg_,2 table as shown in Figure 4. Then Lo
could run Lz using EPTo_,2, which translates directly
from the Lz guest physical address to the Lo host physi-
cal address, reducing the number of page fault exits and
improving nested virtualization performance. In Sec-
tion 4.1.2 we demonstrate more than a three-fold speedup
of some useful workloads with multi-dimensional page
tables, compared to shadow-on-EPT.
The Lo hypervisor launches Lz with an empty EPTo_.2
table, building the table on-the-fly, on Lz EPT-violation
exits. These happen when a translation for a guest phys-
ical address is missing in the EPT table. If there is no
translation in EPT;_,2 for the faulting address, Lo first
lets L; handle the exit and update EPT;_,2. Lo can now
create an entry in EPTp_,2 that translates the Lz guest
physical address directly to the Lo host physical address:
EPT1_,2 is used to translate the L2 physical address to a
L, physical address, and EPTp_,; translates that into the
desired Lo physical address.
To maintain correctness of EPTp_,2, the Lo hypervisor
needs to know of any changes that L; makes to EPT,_,2.
Lg sets the memory area of EPT;_,2 as read-only, thereby
causing a trap when L, tries to update it. Lo will then up-
date EPTg_,2 according to the changed entries in EPT;_,2.
Lo also needs to trap all L; INVEPT instructions, and in-
validate the EPT cache accordingly.
By using huge pages [34] to back guest memory, Lo
can create smaller and faster EPT tables. Finally, to
further improve performance, Lo also allows L; to use
VPIDs. With this feature, the CPU tags each transla-
tion in the TLB with a numeric virtual-processor id,
eliminating the need for TLB flushes on every VMEn-
try and VMExit. Since each hypervisor is free to choose
these VPIDs arbitrarily, they might collide and therefore
Lo needs to map the VPIDs that L; uses into valid Lo
VPIDs.
3.4 I/O: Multi-level Device Assignment
I/O is the third major challenge in server virtualization.
There are three approaches commonly used to provide
I/O services to a guest virtual machine. Either the hyper-
visor emulates a known device and the guest uses an un-
modified driver to interact with it [47], or a para-virtual
driver is installed in the guest [6,42], or the host assigns
a real device to the guest which then controls the device
directly [11,31,37,52,53]. Device assignment generally
provides the best performance [33, 38,53], since it mini-
mizes the number of I/O-related world switches between
the virtual machine and its hypervisor, and although it
complicates live migration, device assignment and live
USENIX Association
migration can peacefully coexist [26, 28,54].
These three basic I/O approaches for a single-level
guest imply nine possible combinations in the two-level
nested guest case. Of the nine potential combinations
we evaluated the more interesting cases, presented in Ta-
ble 1. Implementing the first four alternatives is straight-
forward. We describe the last option, which we call
multi-level device assignment, below. Multi-level de-
vice assignment lets the Lz guest access a device di-
rectly, bypassing both hypervisors. This direct device
access requires dealing with DMA, interrupts, MMIO,
and PIOs [53].
I/O virtualization method
between Lop & Lj
1/O virtualization method
between L; & Lo
Emulation
Para-virtual
Para-virtual
Device assignment
Device assignment
Emulation
Emulation
Para-virtual
Para-virtual
Device assignment
USENIX Association
Table 1: I/O combinations for a nested guest
Device DMA in virtualized environments is compli-
cated, because guest drivers use guest physical addresses,
while memory access in the device is done with host
physical addresses. The common solution to the DMA
problem is an IOMMU [2, 11], a hardware component
which resides between the device and main memory. It
uses a translation table prepared by the hypervisor to
translate the guest physical addresses to host physical
addresses. IOMMUs currently available, however, only
support a single level of address translation. Again, we
need to compress two levels of translation tables onto the
one level available in hardware.
For modified guests this can be done using a paravir-
tual IOMMU: the code in L; which sets a mapping on
the IOMMU from Lz to L; addresses is replaced by a
hypercall to Lo. Lo changes the L; address in that map-
ping to the respective Lo address, and puts the resulting
mapping (from L2 to Lo addresses) in the IOMMU.
A better approach, one which can run unmodified
guests, is for Lp to emulate an IOMMU for L, [5]. Ly
believes that it is running on a machine with an IOMMU,
and sets up mappings from Lz to L, addresses on it. Lo
intercepts these mappings, remaps the L, addresses to
Lo addresses, and builds the L2-to-Lo map on the real
IOMMU.
In current x86 architecture, interrupts always cause a
guest exit to Lo, which proceeds to forward the interrupt
to L;. L; will then inject it into Lg. The EOI (end of
interrupt) will also cause a guest exit. In Section 4.1.1 we
discuss the slowdown caused by these interrupt-related
exits, and propose ways to avoid it.
Memory-mapped I/O (MMIO) and Port I/O (PIO) for
a nested guest work the same way they work for a single-
level guest, without incurring exits on the critical I/O
path [53].
3.5 Micro Optimizations
There are two main places where a guest of a nested hy-
pervisor is slower than the same guest running on a bare-
metal hypervisor. First, the transitions between L; and
Ly are slower than the transitions between Lo and Lj.
Second, the exit handling code running in the L, hyper-
visor is slower than the same code running in Lo. In this
section we discuss these two issues, and propose opti-
mizations that improve performance. Since we assume
that both L, and Lz are unmodified, these optimizations
require modifying Lo only. We evaluate these optimiza-
tions in the evaluation section.
3.5.1 Optimizing transitions between L; and L2
As explained in Section 3.2.3, transitions between L;
and Lz involve an exit to Lo and then an entry. In
Lo, most of the time is spent merging the VMCS’s. We
optimize this merging code to only copy data between
VMCS’s if the relevant values were modified. Keeping
track of which values were modified has an intrinsic cost,
so one must carefully balance full copying versus partial
copying and tracking. We observed empirically that for
common workloads and hypervisors, partial copying has
a lower overhead.
VMCS merging could be further optimized by copy-
ing multiple VMCs fields at once. However, according to
Intel’s specifications, reads or writes to the VMCS area
must be performed using vmread and vmwrite in-
structions, which operate on a single field. We empiri-
cally noted that under certain conditions one could ac-
cess VMCS data directly without ill side-effects, bypass-
ing vmread and vmwrite and copying multiple fields
at once with large memory copies. However, this opti-
mization does not strictly adhere to the VMX specifica-
tions, and thus might not work on processors other than
the ones we have tested.
In the evaluation section, we show that this opti-
mization gives a significant performance boost in micro-
benchmarks. However, it did not noticeably improve the
other, more typical, workloads that we have evaluated.
3.5.2 Optimizing exit handling in L,
The exit-handling code in the hypervisor is slower when
run in L; than the same code running in Lo. The main
cause of this slowdown are additional exits caused by
privileged instructions in the exit-handling code.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) 429
430
In Intel VMX, the privileged instructions vmread and
vmwrite are used by the hypervisor to read and mod-
ify the guest and host specification. As can be seen in
Section 4.3, these cause L; to exit multiple times while
it handles a single L2 exit.
In contrast, in AMD SVM, guest and host specifica-
tions can be read or written to directly using ordinary
memory loads and stores. The clear advantage of that
model is that Lo does not intervene while L; modifies
Lo specifications. Removing the need to trap and emu-
late special instructions reduces the number of exits and
improves nested virtualization performance.
One thing Lo can do to avoid trapping on every
vmread and vmwrite is binary translation [3] of prob-
lematic vmread and vmwrite instructions in the L;
instruction stream, by trapping the first time such an in-
struction is called and then rewriting it to branch to a
non-trapping memory load or store. To evaluate the po-
tential performance benefit of this approach, we tested a
modified L, that directly reads and writes VMCS,_,2 in
memory, instead of using vmread and vmwrite. The
performance of this setup, which we call DRW (direct
read and write) is described in the evaluation section.
4 Evaluation
We start the evaluation and analysis of nested virtual-
ization with macro benchmarks that represent real-life
workloads. Next, we evaluate the contribution of multi-
level device assignment and multi-dimensional paging to
nested virtualization performance. Most of our experi-
ments are executed with KVM as the L, guest hyper-
visor. In Section 4.2 we present results with VMware
Server as the L; guest hypervisor.
We then continue the evaluation with a synthetic,
worst-case micro benchmark running on Lz which
causes guest exits in a loop. We use this synthetic, worst-
case benchmark to understand and analyze the overhead
and the handling flow of a single Lz exit.
Our setup consisted of an IBM x3650 machine booted
with a single Intel Xeon 2.9GHz core and with 3GB of
memory. The host OS was Ubuntu 9.04 with a kernel
that is based on the KVM git tree version kvm-87, with
our nested virtualization support added. For both L, and
Lz guests we used an Ubuntu Jaunty guest with a kernel
that is based on the KVM git tree, version kvm-87. Lj
was configured with 2GB of memory and L2 was config-
ured with 1GB of memory. For the I/O experiments we
used a Broadcom NetXtreme 1Gb/s NIC connected via
crossover-cable to an e1000e NIC on another machine.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
4.1 Macro Workloads
kernbench is a general purpose compilation-type
benchmark that compiles the Linux kernel multiple
times. The compilation process is, by nature, CPU- and
memory-intensive, and it also generates disk I/O to load
the compiled files into the guest’s page cache.
SPECJbb is an industry-standard benchmark de-
signed to measure the server-side performance of Java
run-time environments. It emulates a three-tier system
and is primarily CPU-intensive.
We executed kernbench and SPEC jbb in four se-
tups: host, single-level guest, nested guest, and nested
guest optimized with direct read and write (DRW) as de-
scribed in Section 3.5.2. The optimizations described
in Section 3.5.1 did not make a significant difference to
these benchmarks, and are thus omitted from the results.
We used KVM as both Lo and L; hypervisor with multi-
dimensional paging. The results are depicted in Table 2.
Kernbench
Host Guest | Nested | Nestedprw
Run time 324.3 | 355 406.3 391.5
STD dev. 1.5 10 6.7 3.1
% overhead
vs. host - 9.5 293 20.7
% overhead
vs. guest - - 14.5 10.3
%CPU 93 97 99 99
SPECjbb
Host Guest | Nested | Nestedprw
Score 90493 | 83599 | 77065 | 78347
STD dev. 1104 1230 1716 566
% degradati-
on vs. host - 7.6 14.8 13.4
% degradati-
on vs. guest | - - 7.8 6.3
%CPU 100 100 100 100
Table 2: kernbench and SPEC jbb results
We compared the impact of running the workloads in a
nested guest with running the same workload in a single-
level guest, i.e., the overhead added by the additional
level of virtualization. For kernbench, the overhead
of nested virtualization is 14.5%, while for SPECjbb the
score is degraded by 7.82%. When we discount the
Intel-specific vmread and vmwrite overhead in L,,
the overhead is 10.3% and 6.3% respectively.
To analyze the sources of overhead, we examine the
time distribution between the different levels. Figure 5
shows the time spent in each level. It is interesting to
compare the time spent in the hypervisor in the single-
level case with the time spent in L, in the nested guest
USENIX Association
USENIX Association
case, since both hypervisors are expected to do the same
work. The times are indeed similar, although the L, hy-
pervisor takes more cycles due to cache pollution and
TLB flushes, as we show in Section 4.3. The signifi-
cant part of the virtualization overhead in the nested case
comes from the time spent in Lg and the increased num-
ber of exits.
For SPECjbb, the total number of cycles across
all levels is the same for all setups. This is because
SPEC jbb executed for the same pre-set amount of time
in both cases and the difference was in the benchmark
score.
Efficiently virtualizing a hypervisor is hard. Nested
virtualization creates a new kind of workload for the Lo
hypervisor which did not exist before: running another
hypervisor (Lj) as a guest. As can be seen in Figure 5,
for kernbench Lg takes only 2.28% of the overall cy-
cles in the single-level guest case, but takes 5.17% of the
overall cycles for the nested-guest case. In other words,
Lo has to work more than twice as hard when running a
nested guest.
Not all exits of Lz incur the same overhead, as each
type of exit requires different handling in Lo and Lj. In
Figure 6, we show the total number of cycles required
to handle each exit type. For the single level guest we
measured the number of cycles between VMExit and the
consequent VMEntry. For the nested guest we measured
the number of cycles spent between Lz VMExit and the
consequent Lz VMEntry.
There is a large variance between the handling times
of different types of exits. The cost of each exit comes
primarily from the number of privileged instructions per-
formed by Lj, each of which causes an exit to Lo. For ex-
ample, when L, handles a PIO exit of Lg, it generates on
average 31 additional exits, whereas in the cpuid case
discussed later in Section 4.3 only 13 exits are required.
Discounting traps due to vmread and vmwrite, the
average number of exits was reduced to 14 for PIO and
to 2 for cpuid.
Another source of overhead is heavy-weight exits. The
external interrupt exit handler takes approximately 64K
cycles when executed by Lo. The PIO exit handler takes
approximately 12K cycles when executed by Lo. How-
ever, when those handlers are executed by Lj, they take
much longer: approximately 192K cycles and 183K cy-
cles, respectively. Discounting traps due to vmread
and vmwrite, they take approximately 148K cycles and
130K cycles, respectively. This difference in execution
times between Lo and L; is due to two reasons: first, the
handlers execute privileged instructions causing exits to
Lo. Second, the handlers run for a long time compared
with other handlers and therefore more external events
such as external interrupts occur during their run-time.
4
3
al
0
>
a
Vv
uv
S
3
E
6
Zz
Cue. Nes, Nes Cue. Nes, Nes
‘St G, Ste, Ste, est G5 Ste, Sle,
Cues Cues Cues Cust y
Ry
Kernbench SPECjbb
Figure 5: CPU cycle distribution
200,000
180,000
160,000
140,000 ---
§ 120,000 +--
3
© 100,000 ---
5
5; 80,000 }--
60,000 -- -
40,000 |- -
20,000 [>
0
Bye Pg Rea, CR, A, Se, Wry Peng. Dy, &
er, oO 2d Ay, an. C, Copy, Me Udi Puig “PT.
lal Spo "Cosy ~ Weg Clon “Mg ME j, Olay.
Mery Re Ss Cosy On ‘Sp ‘Nery “tin,
Figure 6: Cycle costs of handling different types of exits
4.1.1 I/O Intensive Workloads
To examine the performance of a nested guest in the
case of I/O intensive workloads we used netperf, a
TCP streaming application that attempts to maximize the
amount of data sent over a single TCP connection. We
measured the performance on the sender side, with the
default settings of netperf (16,384 byte messages).
Figure 7 shows the results for running the netperf
TCP stream test on the host, in a single-level guest, and in
a nested guest, using the five I/O virtualization combina-
tions described in Section 3.4. We used KVM’s default
emulated NIC (RTL-8139), virtio [42] for a paravirtual
NIC, and a 1 Gb/s Broadcom NetXtreme IT with device
assignment. All tests used a single CPU core.
On bare-metal, netperf easily achieved line rate
(940 Mb/s) with 20% CPU utilization.
Emulation gives a much lower throughput, with full
CPU utilization: On a single-level guest we get 25%
of the line rate. On the nested guest the throughput is
even lower and the overhead is dominated by the cost of
device emulation between L; and Ly. Each Lg exit is
trapped by Lo and forwarded to L;. For each Lz exit, Ly
then executes multiple privileged instructions, incurring
multiple exits back to Lo. In this way the overhead for
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI’10) 431
432
IB throughput (Mbps)
Jocpu
1,000 T T 100
“--) 80
+) 60
L
% cpu
“4 40
throughput (Mbps)
wr
S
S
T
Q,. St Sy, Sy, % X
ty, lay “, Y 5 Cy Sy fs
le Offa, ke, Pela, & E& Ge Le,
e ie io Ss
Meh Tle, WE fog bg US US ESI PSN
Oey Le Cea last Cie bee ee ise
ae
Ye, Veg 06,8“ Way, Co CG &
Uy, "On
Figure 7: Performance of net perf in various setups
each Lg exit is multiplied.
The para-virtual virtio NIC performs better than emu-
lation since it reduces the number of exits. Using virtio
all the way up to Lz gives 75% of line rate with a satu-
rated CPU, better but still considerably below bare-metal
performance.
Multi-level device assignment achieved the best per-
formance, with line rate at 60% CPU utilization (Fig-
ure 7, direct/direct). Using device assignment between
Lo and Lj and virtio between L; and L2 enables the L2
guest to saturate the 1Gb link with 92% CPU utilization
(Figure 7, direct/virtio).
While multi-level device assignment outperformed the
other methods, its measured performance is still subop-
timal because 60% of the CPU is used for running a
workload that only takes 20% on bare-metal. Unfortu-
nately on current x86 architecture, interrupts cannot be
assigned to guests, so both the interrupt itself and its EOI
cause exits. The more interrupts the device generates,
the more exits, and therefore the higher the virtualiza-
tion overhead—which is more pronounced in the nested
case. We hypothesize that these interrupt-related exits
are the biggest source of the remaining overhead, so had
the architecture given us a way to avoid these exits—by
assigning interrupts directly to guests rather than having
each interrupt go through both hypervisors—net perf
performance on Lz would be close to that of bare-metal.
To test this hypothesis we reduced the number of in-
terrupts, by modifying standard bnx2 network driver to
work without any interrupts, i.e., continuously poll the
device for pending events
Figure 8 compares some of the I/O virtualization com-
binations with this polling driver. Again, multi-level de-
vice assignment is the best option and, as we hypothe-
sized, this time Lz performance is close to bare-metal.
With netperf’s default 16,384 byte messages, the
throughput is often capped by the 1 Gb/s line rate, so we
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
1000
900
@ 800
&
= 700
3 600
s 500
3 400
E 300 oat LO (bare metal) —+—
500 ae L2 (direct/direct) --»--
neuene? L2 (direct/virtio) -- * - -
100
16 32 64 128 256 512
Message size (netperf -m)
Figure 8: Performance of netperf with interrupt-less
network driver
ran netperf with smaller messages. As we can see in the
figure, for 64-byte messages, for example, on Lo (bare
metal) a throughput of 900 Mb/s is achieved, while on
Lz with multi-level device assignment, we get 837 Mb/s,
a mere 7% slowdown. The runner-up method, virtio on
direct, was not nearly as successful, and achieved just
469 Mb/s, 50% below bare-metal performance. CPU
utilization was 100% in all cases since a polling driver
consumes all available CPU cycles.
4.1.2 Impact of Multi-dimensional Paging
To evaluate multi-dimensional paging, we compared
each of the macro benchmarks described in the previ-
ous sections with and without multi-dimensional paging.
For each benchmark we configured Lo to run L; with
EPT support. We then compared the case where L, uses
shadow page tables to run Lz (“Shadow-on-EPT”’) with
the case of L; using EPT to run Lo (“multi-dimensional
paging”).
3.5
BOY: pee Sa SR 25 MM Sig fot ena ey Stes fae ee ie Sten ae +
DiS p per ee aree ie Seas Se ies tsa ate eR RIS Here Nai Se sie ie RE =]
2 Bs Shadow on EPT
= Multi—dimensional paging
2 2,0). Pest Pan eos BBB SS eb oer ge ete Pee eos eee eS =
a
&
o
3 INS jess Se eae EE Oat Se eee See eee ava see eae eee +
a
—
VO Poort me - - - el e +
0.5 [|---------- AAS - - - - - RR - - - - - - RR) - = = = = +
0.0
kernbench specjbb netperf
Figure 9: Impact of multi-dimensional paging
Figure 9 shows the results. The overhead between the
two cases is mostly due to the number of page-fault exits.
When shadow paging is used, each page fault of the L2
guest results ina VMExit. When multi-dimensional pag-
USENIX Association
ing is used, only an access to a guest physical page that is
not mapped in the EPT table will cause an EPT violation
exit. Therefore the impact of multi-dimensional paging
depends on the number of guest page faults, which is a
property of the workload. The improvement is startling
in benchmarks such as kernbench with a high number
of page faults, and is less pronounced in workloads that
do not incur many page faults.
4.2 VMware Server as a Guest Hypervisor
We also evaluated VMware as the L; hypervisor to ana-
lyze how a different guest hypervisor affects nested vir-
tualization performance. We used the hosted version,
VMWare Server v2.0.1, build 156745 x86-64, on top of
Ubuntu based on kernel 2.6.28-11. We intentionally did
not install VMware tools for the Lz guest, thereby in-
creasing nested virtualization overhead. Due to similar
results obtained for VMware and KVM as the nested hy-
pervisor, we show only kernbench and SPEC jbb re-
sults below.
overhead of handling guest exits in Lo and L;. Based on
this definition, this cpuid micro benchmark is a worst
case workload, since Ly does virtually nothing except
generate exits. We note that cpuid cannot in the gen-
eral case be handled by Lo directly, as L; may wish to
modify the values returned to Lo.
Figure 10 shows the number of CPU cycles required to
execute a single cpuid instruction. We ran the cpuid
instruction 4* 10° times and calculated the average num-
ber of cycles per iteration. We repeated the test for the
following setups: 1. native, 2. running cpuidina single
level guest, and 3. running cpuid ina nested guest with
and without the optimizations described in Section 3.5.
For each execution, we present the distribution of the cy-
cles between the levels: Lo, Li, Lz. CPU mode switch
stands for the number of cycles spent by the CPU when
performing a VMEntry or a VMExit. On bare metal
cpuid takes about 100 cycles, while in a virtual ma-
chine it takes about 2,600 cycles (Figure 10, column 1),
about 1,000 of which is due to the CPU mode switch-
ing. When run in a nested virtual machine it takes about
58,000 cycles (Figure 10, column 2).
Benchmark | % overhead vs. single-level guest
kernbench 14.98
SPEC jbb 8.85
USENIX Association
Table 3: VMware Server as a guest hypervisor
Examining L, exits, we noticed VMware Server
uses VMX initialization instructions (vmon, vmoff,
vmptrid, vmclear) several times during L2 execu-
tion. Conversely, KVM uses them only once. This
dissimilitude derives mainly from the approach used by
VMware to interact with the host Linux kernel. Each
time the monitor module takes control of the CPU, it en-
ables VMX. Then, before it releases control to the Linux
kernel, VMX is disabled. Furthermore, during this tran-
sition many non-VMxX privileged instructions are exe-
cuted by Lj, increasing Lo intervention.
Although all these initialization instructions are emu-
lated by Lo, transitions from the VMware monitor mod-
ule to the Linux kernel are less frequent for Kernbench
and SPECjbb. The VMware monitor module typically
handles multiple Lz exits before switching to the Linux
kernel. As a result, this behavior only slightly affected
the nested virtualization performance.
4.3 Micro Benchmark Analysis
To analyze the cycle-costs of handling a single L2 exit,
we ran a micro benchmark in L2 that does nothing ex-
cept generate exits by calling cpuid ina loop. The vir-
tualization overhead for running an Lg guest is the ratio
between the effective work done by the Lz guest and the
CPU Cycles
60,000
SO000 fetes es en< ewes TMM bane sae so eea baci awetaenawe weaned ence
40,000 }----------------- ABB ---- EBB --- 5-5 eee
Ll
30,000. = yar tig 22 os BBB BRI neg
HB cpu mode switch
20,000 | ----------------- BB --- BB -- - - BB. ------- =
10,000 }----------------- J ----- = Saheaste!
=a
0 : :
2
5 2K, By. LN. SX yy.
Cyl est, Opyhese opNesy Opese.
* d in eq Ptin Ne, Pune,
vey G Men Ge ke Ni
Ue. Ue Ue :
= ons se Ons S . ns 3
Sy ‘S2
Figure 10: CPU cycle distribution for cpuid
To understand the cost of handling a nested guest
exit compared to the cost of handling the same exit for
a single-level guest, we analyzed the flow of handling
cpuid:
L» executes a cpuid instruction
CPU traps and switches to root mode Lo
Lo switches state from running L2 to running L,
. CPU switches to guest mode Lj
L, modifies VMCS1_.2
repeat n times:
ou a et
(a) Ly accesses VMCS1_,2
(b) CPU traps and switches to root mode Lo
(c) Lo emulates VMCSj_,2 access and resumes L,
(d) CPU switches to guest mode Lj
6. L, emulates cpuid for L2
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) 433
434
7. Ly executes a resume of Lo
8. CPU traps and switches to root mode Lo
9. Lo switches state from running L; to running L2
10. CPU switches to guest mode L2
In general, step 5 can be repeated multiple times. Each
iteration consists of a single VMExit from L; to Lo.
The total number of exits depends on the specific im-
plementation of the L; hypervisor. A nesting-friendly
hypervisor will keep privileged instructions to a mini-
mum. In any case, the L; hypervisor must interact with
VMCSj-_.2, as described in Section 3.2.2. In the case of
cpuid, in step 5, Li reads 7 fields of VMCS;_,2, and
writes 4 fields to VMCS1_,2, which ends up as 11 VMEx-
its from L; to Lo. Overall, for a single Lz cpuid exit
there are 13 CPU mode switches from guest mode to
root mode and 13 CPU mode switches from root mode
to guest mode, specifically in steps: 2, 4, Sb, 5d, 8, 10.
The number of cycles the CPU spends in a single
switch to guest mode plus the number of cycles to switch
back to root mode, is approximately 1,000. The total
CPU switching cost is therefore around 13,000 cycles.
The other two expensive steps are 3 and 9. As de-
scribed in Section 3.5, these switches can be optimized.
Indeed as we show in Figure 10, column 3, using various
optimizations we can reduce the virtualization overhead
by 25%, and by 80% when using non-trapping vmread
and vmwrite instructions.
By avoiding traps on vmread and vmwrite (Fig-
ure 10, columns 4 and 5), we removed the exits caused
by VMCSj-,2 accesses and the corresponding VMCS ac-
cess emulation, step 5. This optimization reduced the
switching cost by 84.6%, from 13,000 to 2,000.
While it might still be possible to optimize steps 3
and 9 further, it is clear that the exits of L; while han-
dling a single exit of La, and specifically VMCS accesses,
are a major source of overhead. Architectural support for
both faster world switches and VMCS updates without ex-
its will reduce the overhead.
Examining Figure 10, it seems that handling cpuid
in Ly is more expensive than handling cpuid in Lo.
Specifically, in column 3, the nested hypervisor Ly
spends around 5,000 cycles to handle cpuid, while in
column | the same hypervisor running on bare metal
only spends 1500 cycles to handle the same exit (note
that these numbers do not include the mode switches).
The code running in L, and in Lg is identical; the differ-
ence in cycle count is due to cache pollution. Running
the cpuid handling code incurs on average 5 L2 cache
misses and 2 TLB misses when run in Lo, whereas run-
ning the exact same code in Lj incurs on average 400 L2
cache misses and 19 TLB misses.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
5 Discussion
In nested environments we introduce a new type of work-
load not found in single-level virtualization: the hypervi-
sor as a guest. Traditionally, x86 hypervisors were de-
signed and implemented assuming they will be running
directly on bare metal. When they are executed on top of
another hypervisor this assumption no longer holds and
the guest hypervisor behavior becomes a key factor.
With a nested L; hypervisor, the cost of a single L2
exit depends on the number of exits caused by L; dur-
ing the Lz exit handling. A nesting-friendly L; hyper-
visor should minimize this critical chain to achieve bet-
ter performance, for example by limiting the use of trap-
causing instructions in the critical path.
Another alternative for reducing this critical chain is to
para-virtualize the guest hypervisor, similar to OS para-
virtualization [6, 50,51]. While this approach could re-
duce Lg intervention when L, virtualizes the L2 envi-
ronment, the work being done by Lo to virtualize the
L,; environment will still persist. How much this tech-
nique can help depends on the workload and on the spe-
cific approach used. Taking as a concrete example the
conversion of vmreads and vmwrites to non-trapping
load/stores, para-virtualization could reduce the over-
head for kernbench from 14.5% to 10.3%.
5.1 Architectural Overhead
Part of the overhead introduced with nested virtualization
is due to the architectural design choices of x86 hardware
virtualization extensions.
Virtualization API: Two performance sensitive areas
in x86 virtualization are memory management and I/O
virtualization. With multi-dimensional paging we com-
pressed three MMU translation tables onto the two avail-
able in hardware; multi-level device assignment does
the same for IOMMU translation tables. Architectural
support for multiple levels of MMU and DMA transla-
tion tables—as many tables as there are levels of nested
hypervisors—will immediately improve MMU and I/O
virtualization.
Architectural support for delivering interrupts directly
from the hardware to the Lz guest will remove Lo inter-
vention on interrupt delivery and completion, interven-
tion which, as we explained in Section 4.1.1, hurts nested
performance. Such architectural support will also help
single-level I/O virtualization performance [33].
VMxX features such as MSR bitmaps, I/O bitmaps, and
CR masks/shadows [48] proved to be effective in reduc-
ing exit overhead. Any architectural feature that reduces
single-level exit overhead also shortens the nested critical
path. Such features, however, also add implementation
complexity, since to exploit them in nested environments
USENIX Association
USENIX Association
they must be properly emulated by Lo hypervisors.
Removing the (Intel-specific) need to trap on every
vmread and vmwrite instruction will give an imme-
diate performance boost, as we showed in Section 3.5.2.
Same Core Constraint: The x86 trap-and-emulate
implementation dictates that the guest and hypervisor
share each core, since traps are always handled on the
core where they occurred. Due to this constraint, when
the hypervisor handles an exit the guest is temporarily
stopped on that core. In a nested environment, the L;
guest hypervisor will also be interrupted, increasing the
total interruption time of the Lz guest. Gavrilovska, et
al., presented techniques for exploiting additional cores
to handle guest exits [19]. According to the authors, for
a single level of virtualization, they measured 41% aver-
age improvements in call latency for null calls, cpuid and
page table updates. These techniques could be adapted
for nested environments in order to remove Lo interven-
tions and also reduce privileged instructions call laten-
cies, decreasing the total interruption time of a nested
guest.
Cache Pollution: Each time the processor switches
between the guest and the host context on a single core,
the effectiveness of its caches is reduced. This phe-
nomenon is magnified in nested environments, due to
the increased number of switches. As was seen in Sec-
tion 4.3, even after discounting Lo intervention, the L;
hypervisor still took more cycles to handle an Lg exit
than it took to handle the same exit for the single-level
scenario, due to cache pollution. Dedicating cores to
guests could reduce cache pollution [7, 45, 46] and in-
crease performance.
6 Conclusions and Future Work
Efficient nested x86 virtualization is feasible, despite
the challenges stemming from the lack of architectural
support for nested virtualization. Enabling efficient
nested virtualization on the x86 platform through multi-
dimensional paging and multi-level device assignment
opens exciting avenues for exploration in such diverse
areas as security, clouds, and architectural research.
We are continuing to investigate architectural and
software-based methods to improve the performance
of nested virtualization, while simultaneously exploring
ways of building computer systems that have nested vir-
tualization built-in.
Last, but not least, while the Turtles project is fairly
mature, we expect that the additional public exposure
stemming from its open source release will help enhance
its stability and functionality. We look forward to see-
ing in what interesting directions the research and open
source communities will take it.
Acknowledgments
The authors would like to thank Alexander Graf and Jo-
erg Roedel, whose KVM patches for nested SVM in-
spired parts of this work. The authors would also like
to thank Ryan Harper, Nadav Amit, and our shepherd
Robert English for insightful comments and discussions.
References
[1] Phoenix Hyperspace. http://www.hyperspace.com/.
[2] ABRAMSON, D., JACKSON, J., MUTHRASANALLUR, S.,
NEIGER, G., REGNIER, G., SANKARAN, R., SCHOINAS, L.,
UHLIG, R., VEMBU, B., AND WIEGERT, J. Intel virtualiza-
tion technology for directed I/O. Intel Technology Journal 10, 03
(August 2006), 179-192.
[3] ADAMS, K., AND AGESEN, O. A comparison of software and
hardware techniques for x86 virtualization. SIGOPS Oper. Syst.
Rey. 40, 5 (December 2006), 2-13.
[4] AMD. Secure virtual machine architecture reference manual.
[5] AMIT, N., BEN- YEHUDA, M., AND YASSOUR, B.-A. IOMMU:
Strategies for mitigating the IOTLB bottleneck. In WIOSCA ’J0:
Sixth Annual Workshop on the Interaction between Operating
Systems and Computer Architecture.
[6] BARHAM, P., DRAGOvVIC, B., FRASER, K., HAND, S.,
HARRIS, T., Ho, A., NEUGEBAUER, R., PRATT, I., AND
WARFIELD, A. Xen and the art of virtualization. In SOSP ’03:
Symposium on Operating Systems Principles (2003).
[7] BAUMANN, A., BARHAM, P., DAGAND, P. E., HARRIS, T.,
ISAACS, R., PETER, S., ROSCOE, T., SCHUPBACH, A., AND
SINGHANIA, A. The multikernel: a new os architecture for scal-
able multicore systems. In SOSP ’09: 22nd ACM SIGOPS Sym-
posium on Operating systems principles, pp. 29-44.
[8] BELLARD, F. QEMU, a fast and portable dynamic translator. In
USENIX Annual Technical Conference (2005), p. 41.
[9] BELPAIRE, G., AND HSU, N.-T. Hardware architecture for re-
cursive virtual machines. In ACM ’75: 1975 annual ACM con-
ference, pp. 14-18.
[10] BELPAIRE, G., AND Hsu, N.-T. Formal properties of recur-
sive virtual machine architectures. SIGOPS Oper. Syst. Rev. 9, 5
(1975), 89-96.
[11] BEN-YEHUDA, M., MASON, J., XENIDIS, J., KRIEGER, O.,
VAN DoorRN, L., NAKAJIMA, J., MALLICK, A., AND WAHLIG,
E. Utilizing IOMMUs for virtualization in Linux and Xen. In
OLS ’06: The 2006 Ottawa Linux Symposium, pp. 71-86.
[12] BERGHMANS, O. Nesting virtual machines in virtualization test
frameworks. Master’s thesis, University of Antwerp, May 2010.
[13] BHARGAVA, R., SEREBRIN, B., SPADINI, F., AND MANNE,
S. Accelerating two-dimensional page walks for virtualized sys-
tems. In ASPLOS ’08: 13th intl. conference on architectural sup-
port for programming languages and operating systems (2008).
[14] CLARK, C., FRASER, K., HAND, S., HANSEN, J. G., JUL, E.,
LIMPACH, C., PRATT, I., AND WARFIELD, A. Live migration of
virtual machines. In NSDI ’05: Second Symposium on Networked
Systems Design & Implementation (2005), pp. 273-286.
[15] DEVINE, S. W., BUGNION, E., AND ROSENBLUM, M. Virtu-
alization system including a virtual machine monitor for a com-
puter with a segmented architecture. US #6397242, May 2002.
[16] ForD, B., HIBLER, M., LEPREAU, J., TULLMANN, P., BACK,
G., AND CLAWSON, S. Microkernels meet recursive virtual ma-
chines. In OSDI ’96: Second USENIX symposium on Operating
systems design and implementation (1996), pp. 137-151.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) = 435
436
[17]
[18]
[19]
[20]
[21]
[22]
[23]
[24]
[25]
[26]
[27]
[28]
[29]
[30]
[31]
[32]
[33]
[34]
[35]
[36]
GARFINKEL, T., ADAMS, K., WARFIELD, A., AND FRANKLIN,
J. Compatibility is not transparency: VMM detection myths and
realities. In HOTOS’07: 11th USENIX workshop on Hot topics
in operating systems (2007), pp. 1-6.
GARFINKEL, T., AND ROSENBLUM, M. A virtual machine in-
trospection based architecture for intrusion detection. In Network
& Distributed Systems Security Symposium (2003), pp. 191-206.
GAVRILOVSKA, A., KUMNAR, S., RAJ, H., SCHWAN, K.,
GUPTA, V., NATHUJI, R., NIRANJAN, R., RANADIVE, A., AND
SARAIYA, P. High-performance hypervisor architectures: Virtu-
alization in hpc systems. In HPCVIRT ’07: Ist Workshop on
System-level Virtualization for High Performance Computing.
GEBHARDT, C., AND DALTON, C. Lala: a late launch appli-
cation. In STC ’09: 2009 ACM workshop on Scalable trusted
computing (2009), pp. 1-8.
GOLDBERG, R. P. Architecture of virtual machines. In Proceed-
ings of the workshop on virtual computer systems (New York,
NY, USA, 1973), ACM, pp. 74-112.
GOLDBERG, R. P. Survey of virtual machine research. [EEE
Computer Magazine (June 1974), 34-45.
GRAF, A., AND ROEDEL, J. Nesting the virtualized world.
Linux Plumbers Conference, Sep. 2009.
HE, Q. Nested virtualization on xen. Xen Summit Asia 2009.
HUANG, J.-C., MONCHIERO, M., AND TURNER, Y. Ally: Os-
transparent packet inspection using sequestered cores. In WIOV
*10: The Second Workshop on I/O Virtualization.
HUANG, W., LIU, J., Koop, M., ABALI, B., AND PANDA, D.
Nomad: migrating OS-bypass networks in virtual machines. In
VEE ’07: 3rd international conference on Virtual execution envi-
ronments (2007), pp. 158-168.
INTEL CORPORATION. Intel 64 and IA-32 Architectures Soft-
ware Developers Manual. 2009.
KADAV, A., AND SWIFT, M. M. Live migration of direct-access
devices. In First Workshop on I/O Virtualization (WIOV ’08).
Kivity, A., KAMAY, Y., LAOR, D., LUBLIN, U., AND
LiIGuORI, A. KVM: the linux virtual machine monitor. In Ot-
tawa Linux Symposium (July 2007), pp. 225-230.
LAUER, H. C., AND WYETH, D. A recursive virtual machine
architecture. In Workshop on virtual computer systems (1973),
pp. 113-116.
LEVASSEUR, J., UHLIG, V., STOESS, J., AND GOTZ, S. Un-
modified device driver reuse and improved system dependability
via virtual machines. In OSDI ’04: 6th conference on Symposium
on Opearting Systems Design & Implementation (2004), p. 2.
LEVASSEUR, J., UHLIG, V., YANG, Y., CHAPMAN, M.,
CHUBB, P., LESLIE, B., AND HEISER, G. Pre-virtualization:
Soft layering for virtual machines. In ACSAC ’08: 13th Asia-
Pacific Computer Systems Architecture Conference, pp. 1-9.
Liu, J. Evaluating standard-based self-virtualizing devices: A
performance study on 10 GbE NICs with SR-IOV support. In
IPDPS ’10: IEEE International Parallel and Distributed Pro-
cessing Symposium (2010).
NAVARRO, J., IYER, S., DRUSCHEL, P., AND COX, A. Prac-
tical, transparent operating system support for superpages. In
OSDI ’02: Sth symposium on Operating systems design and im-
plementation (2002), pp. 89-104.
OSISEK, D. L., JACKSON, K. M., AND GUM, P. H. Esa/390
interpretive-execution architecture, foundation for vm/esa. IBM
Systems Journal 30, 1 (1991).
POPEK, G. J., AND GOLDBERG, R. P. Formal requirements for
virtualizable third generation architectures. Commun. ACM 17,7
(July 1974), 412-421.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
[37]
[38]
[39]
[40]
[41]
[42]
[43]
[44]
[45]
[46]
[47]
[48]
[49]
[50]
[51]
[52]
[53]
[54]
RAJ, H., AND SCHWAN, K. High performance and scalable I/O
virtualization via self-virtualized devices. In HPDC ’07: Pro-
ceedings of the 16th international symposium on High perfor-
mance distributed computing (2007), pp. 179-188.
RAM, K. K., SANTOS, J. R., TURNER, Y., Cox, A. L., AND
RIXNER, S. Achieving 10Gbps using safe and transparent net-
work interface virtualization. In VEE ’09: The 2009 ACM SIG-
PLAN/SIGOPS International Conference on Virtual Execution
Environments (March 2009).
RILEY, R., JIANG, X., AND XU, D. Guest-transparent pre-
vention of kernel rootkits with vmm-based memory shadowing.
In Recent Advances in Intrusion Detection, vol. 5230 of Lecture
Notes in Computer Science. 2008, ch. 1, pp. 1-20.
ROBIN, J. S., AND IRVINE, C. E. Analysis of the intel pen-
tium’s ability to support a secure virtual machine monitor. In 9th
conference on USENIX Security Symposium (2000), p. 10.
ROSENBLUM, M. Vmware’s virtual platform: A virtual machine
monitor for commodity pcs. In Hot Chips 11 (1999).
RUSSELL, R. virtio: towards a de-facto standard for virtual I/O
devices. SIGOPS Oper. Syst. Rev. 42, 5 (2008), 95-103.
RUTKOWSKA, J. Subverting vista kernel for fun and profit.
Blackhat, Aug. 2006.
SESHADRI, A., LUK, M., QU, N., AND PERRIG, A. Secvisor: a
tiny hypervisor to provide lifetime kernel code integrity for com-
modity oses. In SOSP ’07: 21st ACM SIGOPS symposium on
Operating systems principles (2007), pp. 335-350.
SHALEV, L., BOROVIK, E., SATRAN, J., AND BEN- YEHUDA,
M. Isostack—highly efficient network processing on dedicated
cores. In USENIX ATC ’10: The 2010 USENIX Annual Technical
Conference (2010).
SHALEV, L., MAKHERVAKS, V., MACHULSKY, Z., BIRAN, G.,
SATRAN, J., BEN- YEHUDA, M., AND SHIMONY, I. Loosely
coupled tcp acceleration architecture. In HOTI ’06: Proceedings
of the 14th IEEE Symposium on High-Performance Interconnects
(Washington, DC, USA, 2006), IEEE Computer Society, pp. 3-8.
SUGERMAN, J., VENKITACHALAM, G., AND LIM, B.-H. Virtu-
alizing I/O devices on VMware workstation’s hosted virtual ma-
chine monitor. In USENIX Annual Technical Conference (2001).
UHLIG, R., NEIGER, G., RODGERS, D., SANTONI, A. L.,
MARTINS, F. C. M., ANDERSON, A. V., BENNETT, S. M.,
KAGI, A., LEUNG, F. H., AND SMITH, L. Intel virtualization
technology. Computer 38, 5 (2005), 48-56.
WALDSPURGER, C. A. Memory resource management in
VMware ESX server. In OSDI ’02: 5th Symposium on Operating
System Design and Implementation.
WHITAKER, A., SHAW, M., AND GRIBBLE, S. D. Denali: a
scalable isolation kernel. In EW ’J0: 10th ACM SIGOPS Euro-
pean workshop (2002), pp. 10-15.
WHITAKER, A., SHAW, M., AND GRIBBLE, S. D. Scale and
performance in the denali isolation kernel. SIGOPS Oper. Syst.
Rev. 36, SI (2002), 195-209.
WILLMANN, P., SHAFER, J., CARR, D., MENON, A., RIXNER,
S., Cox, A. L., AND ZWAENEPOEL, W. Concurrent direct net-
work access for virtual machine monitors. In High Performance
Computer Architecture, 2007. HPCA 2007. IEEE 13th Interna-
tional Symposium on (2007), pp. 306-317.
YASSOUR, B.-A., BEN- YEHUDA, M., AND WASSERMAN, O.
Direct device assignment for untrusted fully-virtualized virtual
machines. Tech. rep., IBM Research Report H-0263, 2008.
ZHAI, E., CUMMINGS, G. D., AND DONG, Y. Live migration
with pass-through device for Linux VM. In OLS ’08: The 2008
Ottawa Linux Symposium (July 2008), pp. 261-268.
USENIX Association
USENIX Association
mClock: Handling Throughput Variability for Hypervisor IO Scheduling
Ajay Gulati
VMware Inc.
Palo Alto, CA, 94304
agulati@vmware.com
Abstract
Virtualized servers run a diverse set of virtual machines
(VMs), ranging from interactive desktops to test and de-
velopment environments and even batch workloads. Hy-
pervisors are responsible for multiplexing the underlying
hardware resources among VMs while providing them
the desired degree of isolation using resource manage-
ment controls. Existing methods provide many knobs
for allocating CPU and memory to VMs, but support for
control of IO resource allocation has been quite limited.
IO resource management in a hypervisor introduces sig-
nificant new challenges and needs more extensive con-
trols than in commodity operating systems.
This paper introduces a novel algorithm for IO re-
source allocation in a hypervisor. Our algorithm,
mClock, supports proportional-share fairness subject to
minimum reservations and maximum limits on the IO
allocations for VMs. We present the design of mClock
and a prototype implementation inside the VMware ESX
server hypervisor. Our results indicate that these rich
QoS controls are quite effective in isolating VM perfor-
mance and providing better application latency. We also
show an adaptation of mClock (called dmClock) for a
distributed storage environment, where storage is jointly
provided by multiple nodes.
1 Introduction
The increasing trend towards server virtualization has el-
evated hypervisors to first class entities in today’s data-
centers. Virtualized hosts run tens to hundreds of virtual
machines (VMs), and the hypervisor needs to provide
each virtual machine with the illusion of owning ded-
icated physical resources: CPU, memory, network and
storage IO. Strong isolation is needed for successful con-
solidation of VMs with diverse requirements on a shared
infrastructure. Existing products such as VMware ESX
server hypervisor provide guarantees for CPU and mem-
ory allocation using sophisticated controls such as reser-
vations, limits and shares [3, 44]. However, the cur-
rent state of the art in storage IO resource allocation
is much more rudimentary, limited to providing propor-
tional shares [20] to different VMs.
IO scheduling in a hypervisor introduces many new
challenges compared to managing other shared re-
Arif Merchant
HP Labs
Palo Alto, CA 94304
arif.merchant@acm.org
Peter J. Varman
Rice University
Houston, TX, 77005
Ppjv@rice.edu
—#-VM1 (Host 1)
-e-VM2 (Host 1)
—-VMB (Host 2)
—*-VM4 (Host 2)
-=- VMS (Host 3)
VM 2,3 start
VM 1 starts
DS2 (Orders per second)
Time
Figure 1: Orders/sec for VM5 decreases as the load on
the shared storage device increases from VMs running
on other hosts.
sources. First, virtualized servers typically access a
shared storage device using either a clustered file system
such as VMFS [11] or NFS volumes. A storage device
in the guest OS or a VM is just a large file on the shared
storage device. Second, the IO scheduler in the hypervi-
sor runs one layer below the elevator-based scheduling
in the guest OS. Hence, it needs to handle issues such as
locality of accesses across VMs, high variability in IO
sizes, different request priorities based on the applica-
tions running in the VMs, and bursty workloads.
In addition, the amount of IO throughput available to
any particular host can fluctuate widely based on the be-
havior of other hosts accessing the shared device. Unlike
CPU and memory resources, the IO throughput avail-
able to a host is not under its own control. As shown
in the example below, this can cause large variations in
the IOPS available to a VM and impact application-level
performance.
Consider the simple scenario shown in Figure 1, with
three hosts and five VMs. Each VM is running a DVD-
Store [2] benchmark, which is an IO-intensive OLTP
workload. The system administrator has carefully pro-
visioned the resources (CPU and memory) needed by
VM 5, so that it can serve at least 400 orders per second.
Initially, VM 5 is running on host 3, and it achieves a
transaction rate of roughly 500 orders/second. Later, as
four other VMs (1 — 4), running on two separate hosts
sharing the same storage device, start to consume IO
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) 437
438
bandwidth, the transaction rate of VM 5 drops to 275
orders per second, which is significantly lower than ex-
pected. Other events that can cause this sort of fluctua-
tion are: (1) changes in workloads (2) background tasks
scheduled at the storage array, and (3) changes in SAN
paths between the hosts and storage device.
PARDA [20] provided a distributed control algorithm
to allocate queue slots at the storage device to hosts in
proportion to the aggregate IO shares of the VMs run-
ning on them. The local IO scheduling at each host
was done using SFQ(D) [24] a traditional fair-scheduler,
which divides the aggregate host throughput among the
VMs in proportion to their shares. Unfortunately, as ag-
gregate throughput fluctuates downwards, or as the value
of a VM’s shares is diluted by the addition of other VMs
to the system, the absolute throughput for a VM falls.
This open-ended dilution is unacceptable in many appli-
cations that require minimum resource requirements to
function. Lack of QoS support for IO resources can have
widespread effects, rendering existing CPU and mem-
ory controls ineffective when applications block on IO
requests. Arguably, this limitation is one of the reasons
for the slow adoption of IO-intensive applications in vir-
tualized environments.
Resource controls such as shares (a.k.a. weights),
reservations, and limits are used for predictable service
allocation with strong isolation [8, 34, 43, 44]. Shares
are a relative allocation measure that specify the ratio in
which the different VMs receive service. Reservations
and limits are expressed in absolute units, e.g. CPU cy-
cles/sec or megabytes of memory. The general idea is to
allocate the resource to the VMs in proportion to their
shares, subject to the constraints that each VM receives
at least its reservation and no more than its limit. These
controls have primarily been employed for allocating re-
sources like CPU time and memory pages where the re-
source capacity is known and fixed.
For fixed-capacity resources, one can combine shares
and reservations into one single allocation for a VM.
This allocation can be calculated whenever a new VM
enters or leaves the system, since these are the only
events at which the allocation is affected. However, en-
forcing these controls is much more difficult when the
capacity fluctuates dynamically, as is the case for the IO
bandwidth of shared storage. In this case the allocations
need to be continuously monitored (rather than only at
VM entry and exit) to ensure that no VM falls below
its minimum. A brute-force solution is to emulate the
method used for fixed-capacity resources by recomput-
ing the allocations periodically. However this method
relies on accurately being able to predict future capacity
based on the current state.
Finally, /imits provide an upper bound on the absolute
resource allocations. Such a limit on IO performance
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
VMs _<reservation,
limit,
LK shares>
roc <> ESI
: SAN fabric
Virtualized Host Storage Array
Figure 2: Virtualized host with VMs accessing a shared
storage array over a SAN
is desirable to prevent competing IO-intensive applica-
tions, such as virus scanners, virtual-disk migrations, or
backup operations, from consuming all the spare band-
width in the system, which can result in high latencies
for bursty and ON-OFF workloads. There are yet other
reasons cited by service providers for wanting to explic-
itly limit IO throughput; for example, to avoid giving
VMs more throughput than has been paid for, or to avoid
raising expectations on performance that cannot gener-
ally be sustained [1, 8].
In this paper, we present mClock, an IO scheduler that
provides all three controls mentioned above at a per- VM
level (Figure 2). We believe that mClock is the first
scheduler to provide such controls in the presence of
capacity fluctuations at short time scales. We have im-
plemented mClock, along with certain storage-specific
optimizations, as a prototype scheduler in the VMware
ESX server hypervisor and showed its effectiveness for
various use cases.
We also demonstrate dmClock, a distributed version
of the algorithm that can be used in clustered storage
systems, where the storage is distributed across multiple
nodes (e.g., LeftHand [4], Seanodes [6], IceCube [46],
FAB [30]). dmClock ensures that the overall alloca-
tion to each VM is based on the specified shares, reser-
vations, and limits even when the VM load is non-
uniformly distributed across the storage nodes.
The remainder of the paper is organized as follows. In
Section 2 we discuss mClock’s scheduling goal and its
comparison with existing approaches. Section 3 presents
the mClock algorithm in detail, along with storage-
specific optimizations. Distributed implementation for
a clustered storage system is discussed in Section 3.2.
Detailed performance evaluation using a diverse set of
workloads is presented in Section 4. Finally we con-
clude with some directions for future work in Section 5.
2 Overview and Related Work
The work related to QoS-based IO resource allocation
can be divided into three broad areas. First is the class
of algorithms that provide proportional allocation of IO
USENIX Association
Algorithm class Proportional | Latency Reservation | Limit Handle Capacity
allocation support i — ——
| Proportional Sharing (PS) Algorithms | Sharing | Proportional Sharing (PS) Algorithms | Algorithms | Yes }No | |}No | No |No
PS + Reservations Yes Yes Yes
mClock Yes Yes Yes = =
Table 1: Comparison of mClock with existing scheduling techniques
USENIX Association
1400
& 56 HRD (R=250, W=100, L=Inf) I
= : San ie
|= Seen
x
< 800
2 600
eI
>
3 400
eT fl i i
> a
500 700 875 1,500 2,000 2,500 3,000
Total Throughput T (IOPS)
Figure 3: Allocation of IOPS to various VMs as the
overall throughput changes
resources, such as Stonehenge [23] SFQ(D) [24], Ar-
gon [41], and Aqua [48]. Many of these algorithms are
variants of weighted fair queuing mechanisms (Virtual
Clock [50], WFQ [13], PGPS [29], WF7Q [10], SCFQ
[15], Leap Forward [38], SFQ [18] and Latency-rate
scheduling [33]) proposed in the networking literature,
adapted to handle various storage-specific concerns such
as concurrency, minimizing seek delays and improving
throughput.
The goal of these algorithms is to allocate through-
put or bandwidth in proportion to the specified weights
of the clients. Second is the class of algorithms that
provide support for latency-sensitive applications along
with proportional sharing. These algorithms include
SMART [28], BVT [14], pClock [22], Avatar [49] and
service curve based techniques [12, 27,31, 36]. Third
is the class of algorithms that support reservation along
with proportional allocation, such as Rialto [25], ESX
memory management [44] and other reservation based
CPU scheduling methods [17, 34,35]. Table 1 provides
a quick comparison of mClock with existing algorithms
in the three categories.
2.1 Scheduling Goals of mClock
We first discuss a simple example describing the
scheduling policy of mClock. As mentioned earlier,
three parameters are specified for each VM in the sys-
tem: a share or weight represented by wj, a reservation
r;, and a limit l;. We assume these parameters are exter-
nally provided; determining the appropriate parameter
settings to meet application requirements is an important
but separate problem, outside the scope of this paper. We
also assume that the system includes an admission con-
trol component that ensures that the system capacity is
adequate to serve the aggregate minimum reservations
of all admitted clients. The behavior of the system if the
assumption does not hold is discussed later in the sec-
tion, along with alternative approaches.
Consider a simple setup with three VMs: one sup-
porting remote desktop (RD), one running an Online
Transaction Processing (OLTP) application and a Data
Migration (DM) VM. The RD VM has a low through-
put requirement but needs low IO latency for usability.
OLTP runs a transaction processing workload requiring
high throughput and low IO latency. The data migration
workload requires high throughput but is insensitive to
IO latency. Based on these requirements, the shares for
RD, OLTP, and DM can be assigned as 100, 200, and
300 respectively. To provide low latency and a minimum
degree of responsiveness, reservations of 250 IOPS each
are specified for RD and OLTP. An upper limit of 1000
IOPS is set for the DM workload so that it cannot con-
sume all the spare bandwidth in the system and cause
high delays for the other workloads. The values chosen
here are somewhat arbitrary, but were selected to high-
light the use of various controls in a diverse workload
scenario.
First consider how a conventional proportional sched-
uler would divide the total throughput 7 of the storage
device. Since throughput is allocated to VMs in pro-
portion to their weights, an active VM v; will receive
a throughput T x (w;/;w;), where the summation is
over the weights of the active VMs (i.e. those with at
least one pending IO). If the storage device’s through-
put is 1200 IOPS in the above example, RD will re-
ceive 200 IOPS, which is below its required minimum
of 250 IOPS. This can lead to a poor experience for the
RD user, even though there is sufficient system capac-
ity for both RD and OLTP to receive their reservations
of 250 IOPS. In our model, VMs always receive service
between their minimum reservation and maximum limit
(as long as system throughput is at least the aggregate of
the reservations of active VMs).
In this case, mclock would provide RD with its min-
imum reservation of 250 IOPS and the remaining 950
IOPS would be divided between OLTP and DM in the
ratio 2 : 3, resulting in allocations of 380 and 570 IOPS
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) 439
440
respectively. Figure 3 shows the IOPS allocation to the
three VMs in the example above, for different values of
the system throughput, T. For T between 1500 and 2000
IOPS, the throughput is shared between RD, OLTP, and
DM in proportion to their weights (1 : 2 : 3), since none
of them will exceed their limit or fall below the reser-
vation. If T > 2000 IOPS, then DM will be capped at
1000 IOPS because its share of T /2 is higher than its up-
per limit, and the remainder is divided between RD and
OLTP in the ratio 1 : 2. If the total throughput T drops
below 1500 IOPS, the allocation of RD bottoms out at
250 IOPS, and similarly at T < 875 IOPS, OLTP also
bottoms out at 250 IOPS. Finally, for T < 500 IOPS, the
reservations of RD and OLTP cannot be met; the avail-
able throughput will be divided equally between RD and
OLTP (since their reservations are the same) and DM
will receive no service. The last case should be rare if
the admission controller estimates the overall through-
put conservatively.
The allocation to a VM varies dynamically with
the current throughput T and the set of active VMs.
At any time, the VMs are partitioned into three sets:
reservation-clamped (&), limit-clamped (&) or propor-
tional (#), based on whether their current allocation
is clamped at the lower or upper bound or is in be-
tween. If T is the current throughput, we define Tp =
T —Yiew~rj — Ujev!;. The allocation y; made to active
VM v; for Tp > 0, is given by:
Tj VIE &
% = lj yeZ (1)
Tp x (wi/Ljewwj) Vie FP
and
Le = Ss (2)
When the system throughput T is known, the alloca-
tions y; can be computed explicitly. Such explicit com-
putation is sometimes used for calculating CPU time al-
locations to virtual machines with service requirement
specifications similar to these. When a VM exits or is
powered on at the host, new service allocations are com-
puted. In the case of a storage array, T is highly de-
pendent on the presence of other hosts and the work-
load presented to the storage device. Since the through-
put varies dynamically, the storage scheduler cannot rely
upon service allocations computed at VM entry and exit
times. The mClock scheduler ensures that the goals in
Eq. (1) and (2) are satisfied continuously, even as the
system’s throughput varies, using a novel, lightweight
tagging scheme.
Clearly, a feasible allocation is possible only if the ag-
gregate reservation )' rj; does not exceed the total sys-
tem throughput 7. When 7p < 0, the system through-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
put is insufficient to meet the reservations; in this case
mClock simply gives each VM throughput proportional
to its reservation. This may not always be the desired be-
havior. VMs without a reservation may be starved in this
case, but this problem can be easily avoided by adding
a small default reservation for all VMs. In addition, one
can add priority control to meet reservations based on
priority levels. Exploring these options further is left to
future work.
2.2 Proportional Share Algorithms
A number of approaches such as Stonehenge [23],
SFQ(D) [24] and Argon [41] have been proposed for
proportional sharing of storage between applications.
Wang and Merchant [45] extended proportional sharing
to distributed storage. Argon [41] and Aqua [48] pro-
pose service-time-based disk allocation to provide fair-
ness as well as high efficiency. Brandt et al. [47] have
proposed Hierarchical Disk Sharing, which uses hier-
archical token buckets to provide isolation and band-
width reservation among clients accessing the same disk.
However, measuring per-request service times in our en-
vironment is difficult because multiple requests will typ-
ically be pending at the storage device.
Overall, none of these algorithms offers support for
the combination of shares, reservations, and limits.
Other methods for resource management in virtual clus-
ters [16,39] have been proposed, but they mainly focus
on CPU and memory resources and do not address the
challenges raised by variable capacity that mClock does.
2.3. Latency-sensitive Application Support
Several existing algorithms provide support for con-
trolling the response time of latency-sensitive applica-
tions, but not strict latency guarantees or explicit la-
tency targets. In the case of CPU scheduling, BVT [14],
SMART [28], and lottery scheduling [37, 43] provide
proportional allocation, latency-reducing mechanisms,
and methods to handle priority inversion by exchanging
tickets. Borrowed Virtual Time [14] and SMART [28]
can give a short-term advantage to latency-sensitive ap-
plications by shifting their virtual tags relative to the
other applications. pClock [22] and service-curve based
methods [12, 27,31, 36] decouple latency and through-
put requirements, but like the other methods also do not
support reservations and limits.
2.4 Reservation-Based Algorithms
For CPU scheduling and memory management, several
approaches have been proposed for integrating reserva-
tions with proportional-share allocations [17, 34,35]. In
these models, clients either receive a guaranteed frac-
tion of the server capacity (reservation-based clients) or
a share (ratio) of the remaining capacity after satisfying
USENIX Association
USENIX Association
reservations (proportional-share-based clients). A stan-
dard proportional-share scheduler can be used in con-
junction with an allocator that adjusts the weights of the
active clients whenever there is a client arrival or depar-
ture. Guaranteeing minimum allocations for CPU time
is relatively straightforward since its capacity (in terms
of cycles/sec) is fixed and known, and allocating a given
proportion would guarantee a certain minimum amount.
The same idea does not apply to storage allocation where
system throughput can fluctuate.
In our model the clients are not statically par-
titioned into reservation-based or proportional-share-
based clients. Our model automatically modifies the en-
titlement of a client when service capacity changes due
to changes in the workload characteristics or due to the
arrival or departure of clients. The entitlement is at least
equal to the reservation and can be higher if there is suf-
ficient capacity. Since 2003, the VMware ESX Server
has provided reservations and proportional-share con-
trols for both CPU and memory resources in a commer-
cial product [8,42,44]. These mechanisms support the
same rich set of controls as in mClock, but do not handle
varying service capacity.
Finally, operating system based frameworks like Ri-
alto [25] provide fixed reservations for known-capacity
CPU service, while allowing additional service requests
to be honored on an availability basis. Rialto requires re-
computation of an allocation graph on each new arrival,
which is then used for CPU scheduling.
3. mClock Algorithm
Tag-based scheduling underlies many previously pro-
posed fair-schedulers [10, 13, 15, 18]: all requests are as-
signed tags and scheduled in order of their tag values.
For example, an algorithm can assign tags spaced by in-
crements of 1/w; to successive requests of client i; if all
requests are scheduled in order of their tag values, the
clients will receive service in proportion to w;. In order
to synchronize idle clients with the currently active ones,
these algorithms also maintain a global tag value com-
monly known as global virtual time or just virtual time.
In mClock, we extend this notion to use multiple tags
based on three controls and dynamically decide which
tag to use for scheduling, while still synchronizing idle
clients.
The intuitive idea behind the mClock algorithm is to
logically interleave a constraint-based scheduler and a
weight-based scheduler in a fine-grained manner. The
constraint-based scheduler ensures that VMs receive at
least their minimum reserved service and no more than
the upper limit in a time interval, while the weight-based
scheduler allocates the remaining throughput to achieve
proportional sharing. The scheduler alternates between
phases during which one of these schedulers is active to
Weight of VM v;
Reservation of VM v;
Maximum service allowance (Limit) for v;
Table 2: Symbols used and their descriptions
maintain the desired allocation.
mClock uses two main ideas: multiple real-time
clocks and dynamic clock selection. Each VM IO re-
quest is assigned three tags, one for each clock: a reser-
vation tag R, a limit tag L, and a proportional share tag P
for weight-based allocation. Different clocks are used to
keep track of each of the three controls, and tags based
on one of the clocks are dynamically chosen to do the
constraint-based or weight-based scheduling.
The scheduler has three main components: (7) Tag As-
signment (i) Tag Adjustment and (iii) Request Schedul-
ing. We will explain each of these in more detail below.
Tag Assignment: This routine assigns R, L and P tags
to a request r from VM vy, arriving at time f. All the tags
are assigned using the same underlying principle, which
we illustrate here using the reservation tag. The R tag
assigned to this request is the higher of the arrival time
or the previous R tag + 1/r;. That is:
R= max{R)! +1/r;, Current time} (3)
This gives us two key properties: first, the R tags of
a continuously backlogged VM are spaced 1/r; apart.
In an interval of length T, a backlogged VM will have
about T x r; requests with R tag values in that interval.
Second, if the current time is larger than this value due
to v; becoming active after a period of inactivity, the re-
quest is assigned an R tag equal to the current time. Thus
idle VMs do not gain any idle credit for future service.
Similarly, the Z tag is set to the maximum of the cur-
rent time and (L'~' + 1/1;). The L tags of a backlogged
VM are spaced out by 1 //;. Hence, if the L tag of the first
pending request of a VM is less than the current time, it
has received less than its upper limit at this time. A limit
tag higher than the current time would indicate that the
VM has received its limit and should not be scheduled.
The proportional share tag P’ is also the larger of the
arrival time of the request and (P’—! + 1/w;) and subse-
quent backlogged requests are spaced by 1 /wj.
Tag Adjustment: Tag adjustment is used to calibrate
the proportional share tags against real time. This is re-
quired whenever an idle VM becomes active again. In
virtual time based schedulers [10, 15] this synchroniza-
tion is done using global virtual time. The initial P tag
value of a freshly active VM is set to the current time,
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI’10) 441
442
but the spacing of P tags after that is determined by the
relative weights of the VMs. After the VM has been ac-
tive for some time, the P tag values become unrelated to
real time. This can lead to starvation when a new VM
becomes active, since the existing P tags are unrelated
to the P tag of the new VM. Hence existing P tags are
adjusted so that the smallest P tag matches the time of
arrival of the new VM, while maintaining their relative
spacing. In the implementation, when a VM is acti-
Algorithm 1: Components of mClock algorithm
Max_QueueDepth = 32;
RequestArrival (request r, time t, vm v;)
begin
if v; was idle then
/* Tag Adjustment */
minPtag = minimum of all P tags;
foreach active VM v; do
LL P; — = minPtag — t;
/* Tag Assignment */
Ri = max{R)! + 1/r;, t} /* Reservation tag */
Li =max{L)!+1/l;, t} /* Limit tag */
Pr= max{P/—! +1/wj, t}/* Shares tag */
ScheduleRequest();
end
ScheduleRequest ()
begin
if Active IOs > Max_QueueDepth then
|_ return;
Let E be the set of requests with R tag < t
if E not empty then
/* constraint-based scheduling */
select IO request with minimum R tag from
E
else
/* weight-based scheduling */
Let E’ be the set of requests with L tag < t
if E’ not empty OR Active JOs == 0 then
select IO request with minimum P tag
from E’
/* Assuming request belong to VM v, */
Subtract 1/r; from R tags of VM vx
if JO request selected != NULL then
| Active IOs++;
end
RequestCompletion (request r, vm v;)
Active_IOs —— ;
ScheduleRequest();
vated, we assign it an offset equal to the difference be-
tween the effective value of the smallest existing P tag
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
and the current time. During scheduling, the offset is
added to the P tag to obtain the effective P tag value.
The relative ordering of existing P tags is not altered by
this transformation; however, it ensures that the newly
activated VMs compete fairly with existing VMs.
Request Scheduling: mClock needs to check three dif-
ferent tags to make its scheduling decision instead of
a single tag in previous algorithms. As noted earlier,
the scheduler alternates between constraint-based and
weight-based phases. First, the scheduler checks if there
are any eligible VMs with R tags no more than the cur-
rent time. If so, the request with smallest R tag is dis-
patched for service. This is defined as the constraint-
based phase. This phase ends (and the weight-based
phase begins) at a scheduling instant when all the R tags
exceed the current time.
During a weight-based phase, all VMs have received
their reservations guaranteed up to the current time. The
scheduler therefore allocates server capacity to achieve
proportional service. It chooses the request with small-
est P tag, but only from VMs which have not reached
their limit (whose L tag is smaller than the current
time). Whenever a request from VM v1; is scheduled in
a weight-based phase, the R tags of the outstanding re-
quests of v; are decreased by 1/r;. This maintains the
condition that R tags are always spaced apart by 1/7r;, so
that reserved service is not affected by the service pro-
vided in the weight-based phase. Algorithm | provides
pseudo code of various components of mClock.
3.1 Storage-specific Issues
There are several storage-specific issues that an IO
scheduler needs to handle: IO bursts, request types, IO
size, locality of requests and reservation settings.
Burst Handling. Storage workloads are known to be
bursty, and requests from the same VM often have a high
spatial locality. We help bursty workloads that were idle
to gain a limited preference in scheduling when the sys-
tem next has spare capacity. This is similar to some of
the ideas proposed in BVT [14] and SMART [28]. How-
ever, we do it in a manner so that reservations are not
impacted.
To accomplish this, we allow VMs to gain idle cred-
its. In particular, when an idle VM becomes active, we
compare the previous P tag with current time ¢ and al-
low it to lag behind t by a bounded amount based on
a VM-specific burst parameter. Instead of setting the P
tag to the current time, we set it equal tot — 0; *(1/wj).
Hence the actual assignment looks like:
Pr = max{P"! +1/wi, t— o;/wi}
The parameter o; can be specified per VM and deter-
mines the maximum amount of credit that can be gained
by becoming idle. Note that adjusting only the P tag
USENIX Association
USENIX Association
has the nice property that it does not affect the reserva-
tions of other VMs; however if there is spare capacity in
the system, it will be preferentially given to the VM that
was idle. This is because the R and L tags have strict
priority over the P tags, so adjusting P tags cannot affect
the constraint-based phase of the scheduler.
Request Type. mClock treats reads and writes iden-
tically. In practice writes show lower latency due to
write buffering in the disk array. However doing any
re-ordering of reads before writes for a single VM can
lead to an inconsistent state of the virtual disk on a crash.
Hence mClock schedules all IOs within a VM in a FCFS
order without distinguishing between reads and writes.
IO size. Since larger IO sizes take longer to complete,
differently-sized IOs should not be treated equally by the
IO scheduler. We propose a technique to handle large-
sized IOs during tagging. The IO latency with n random
outstanding IOs with an IO size of S each can be written
as:
Lat = n(Tn + S/B peak) (4)
Here 7, denotes the mechanical delay due to seek and
disk rotation and Byeax denotes the peak transfer band-
width of a disk. Converting the latency observed for an
10 of size S; to an IO of a reference size S2, keeping
other factors constant would give:
So Si
Laty = Lat, * (1+ ————) /(1++ —————) ©)
( Tr, X Bye’ ' Tin X Bpoa”
For a small reference IO size of 8KB and using typical
values for mechanical delay 7,, = Sms and peak trans-
fer rate, Byeak = 60 MB/s, the numerator = Lat*(1
+ 8/300) = Lat;. So, for tagging purposes, a sin-
gle request of IO size S is treated as equivalent to:
(1+ S/(Tm X Boeak)) IO requests.
Request Location. mClock can detect sequentiality
within a VM’s workload, but in most virtualized envi-
ronments the IO stream seen by the underlying storage
may not be sequential due to a high degree of multiplex-
ing. mClock improves the overall efficiency of the sys-
tem by scheduling IOs with high locality as a batch. A
VM is allowed to issue IO requests in a batch as long
as the requests are close in logical block number space
(e.g., within 4 MB). Also the size of batch is bounded by
a configurable parameter (set to 8).
This optimization impacts the time granularity over
which reservations are met. The batching of IOs is lim-
ited to a small number, typically 8. so for N VMs, the
delay in meeting reservations can be 8N IOs. A typical
number of VMs/host is 10-15, so this can delay reserva-
tion guarantees in the short term by the time taken to do
roughly 100 IOs. Note that the benefit of batching and
improved efficiency is distributed among all the VMs in-
stead of giving it just to the VM with high sequentiality.
It may be preferable to allocate the benefit of locality to
the concerned VM; this is deferred to future work.
Reservation Setting. Admission control is a well
known and difficult problem for storage devices due to
their stateful nature and dependence of the throughput
on the workload. We propose the simple approach of us-
ing the worst case IOPS from a storage device as an up-
per bound on sum of reservations for admission control.
For example, an enterprise FC disk can service 200 to
250 random IOPS and a SATA disk can do roughly 80-
100 IOPS. Based on the number and type of disk drives
backing a storage LUN, one can obtain a conservative
estimate of reservable throughput. This is what we have
used to set parameters in our experiments. Also in order
to set the reservations to meet an application’s latency
for a certain number of outstanding IOs, we use Little’s
law:
IOPS = Outstanding IOs/Latency (6)
Thus, for an application that typically keeps 8 IOs out-
standing and requires 25 ms average latency, the reser-
vation should be set to 8 / 0.025 = 320 IOPS.
3.2 Distributed mClock
Cluster-based storage systems are emerging as a cost-
effective, scalable alternative to expensive, centralized
disk arrays. By using commodity hardware (both hosts
and disks) and using software to glue together the stor-
age distributed across the cluster, these systems allow
for lower cost and more flexible provisioning than con-
ventional disk arrays. The software can be designed to
compensate for the reliability and consistency issues in-
troduced by the distributed components.
Several research prototypes (e.g., CMU’s Ursa Mi-
nor [9], HP Labs’ FAB [30], IBM’s Intelligent
Bricks [46]) have been built, and several companies
(such as LeftHand [4], Seanodes [6]) are offering iSCSI-
based storage devices using local disks at virtualized
hosts. In this section, we extend mClock to run on each
storage server, with minimal communication between
the servers, and yet provide per-VM globally (cluster-
wide) proportional service, reservations, and limits.
3.2.1 dmClock Algorithm
dmClock runs a modified version of mClock at each
server. There is only one modification to the algorithm to
account for the distributed model in the Tag-Assignment
component. During tag assignment each server needs to
determine two things: the aggregate service received by
the VM from all the servers in the system and the amount
of service that was done as part of reservation. This in-
formation will be provided implicitly by the host run-
ning a VM by piggybacking two integers p; and 6; with
each request that it forwards to a storage server s;. Here
6; denotes number of IO requests from VM v; that have
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) 443
444
completed service at all the servers between the previous
request (from v,) to the server s; and the current request.
Similarly, p; denotes the number of IO requests from v;
that have been served as part of constraint-based phase
between the previous request to s; and the current re-
quest. This information can be easily maintained by the
host running the VM. The host forwards the values of
p; and 6; along with v,;’s request to a server. (Note that
for the single server case, p and 6 will always be 1.)
In the Tag-Assignment routine, these values are used to
compute the tags as follows:
Ri = max{R7' +pi/ri, t}
LEo= max{i?'+6/h;, t}
Pr = max{P!!+6;/wi, t}
Hence, the new request may receive a tag further into
the future, to reflect the fact that v; has received addi-
tional service at other servers. The greater the value of
6, the lower the priority the request has for service. Note
that this does not require any synchronization among the
storage servers. The remainder of the algorithm remains
unchanged. The values of p and 6 may, in the worst
case, be inaccurate by up to | request at each of the other
servers. However, the dmClock algorithm does not re-
quire complex synchronization between the servers [32].
4 Performance Evaluation
In this section, we present results from a detailed evalu-
ation of mClock using a prototype implementation in the
VMware ESX server hypervisor [7,40]. The changes
required were small: the overall implementation took
roughly 200 lines of C code in order to modify an ex-
isting scheduling framework. The resulting scheduler is
lightweight, which is important because it is on the crit-
ical path for IO issues and completions. We examine the
following key questions about mClock:
(1) Why is mClock needed? (2) Can mClock allo-
cate service in proportion to weights, while meeting the
reservation and limit constraints? (3) Can mClock han-
dle bursts effectively and reduce latency by giving idle
credit? (4) How effective is dmClock in providing isola-
tion among dynamic workloads in a distributed storage
environment?
4.1 Experimental Setup
We implemented mClock by modifying the SCSI
scheduling layer in the IO stack of VMware ESX server
hypervisor to construct our prototype. The ESX host
was a Dell Poweredge 2950 server with 2 Intel Xeon
3.0 GHz dual-core processors, 8GB of RAM and two
Qlogic HBAs connected to an EMC CLARION CX3-
40 storage array over FC SAN. We used two different
storage volumes: one hosted on a 10 disk RAID 0 disk
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
group and another on a 10 disk, RAID 5 disk group. The
host was configured to keep 32 IOs pending per LUN at
the array, which is the default setting.
We used a diverse set of workloads, using different
operating systems, workload generators, and configura-
tions, to verify that mClock is robust under a variety
of conditions. We used two kinds of VMs: (1) Linux
(RHEL) VMs, each with a 10GB virtual disk, one VCPU
and 512 MB memory, and (2) Windows server 2003
VMs, each with a 16GB virtual disk, one VCPU and 1
GB of memory. The disks hosting the operating systems
for VMs were on a different storage LUN.
Three parameters were configured for each VM: a
minimum reservation 7; IOPS, a global weight w;, and
maximum limit /; IOPS. The workloads were gener-
ated using Iometer [5] in the Windows server VMs
and our own micro-workload generator in the Linux
RHEL VMs. For both cases, the workloads were spec-
ified using IO sizes, the percentage of reads, the per-
centage of random IOs, and the number of concur-
rent IOs. We used 32 concurrent IOs per workload in
all experiments, unless otherwise stated. In addition
to these micro-benchmark workloads, we used macro-
benchmark workloads generated using Filebench [26].
2000
—
a
So
oOo
1000
a
So
oOo
Average Throughput (IOPS)
20 40 60 80 100 120 140 160 180
Time (s)
Figure 5: mClock limits the throughput of VM2 and
VM3 to 400 and 500 IOPS as desired.
4.1.1 Limit Enforcement
First we show the need for the limit control by demon-
strating that pure proportional sharing cannot guarantee
the specified number of IOPS and latency to a VM. We
experimented with three workloads similar to those in
the example of Section 2: RD, OLTP and DM.
RD is a bursty workload sending 32 random IOs (75%
reads) of 4KB size every 250 ms. OLTP sends 8KB ran-
dom IOs, 75% reads, and keeps 16 IOs pending at all
times. The data migration workload DM does 32KB se-
quential reads, and keeps 32 IOs pending at all times.
RD and OLTP are latency-sensitive workloads, requiring
a response time under 30ms, while DM is not sensitive
to latency. Accordingly, we set the weights in the ratio
USENIX Association
USENIX Association
1200 +
n
a
© 1000 4
= x. Menage,
5 * "
2 800 1
2
© 600"
F x
ton = 3
® 400 * = DM gets only 300 IOPS
© Bgl
og - 4
< a ee a
0 1 ‘ n 1 n 1 1
60 80 100 120 140 160 180 200 220
Time (s)
(a) Throughput
140 | VDI (w=2) +
Ss
pe
oY,
So 36
7
7
=
foe}
oO
T
A Latency improves
, forOLTP,VDI ;
Average Latency (ms)
k DD
oO oO
20
0 r 1 1 x n 1 n
60 80 100 120 140 160 180 200 220
Time (s)
(b) Average Latency
Figure 4: Average throughput and latency for RD, OLTP and DM workloads, with weights = 2:2:1. At t=140 the
limit for DM is set to 300 IOPS. mClock is able to restrict the DM workload to 300 IOPS and improve the latency of
RD and OLTP workloads.
Total throughput (SFQ) —+—
VM1 (w=1) —+— VM1 (r=300, w=1) —+—
ao Total throughput (mClock) 3 @ 1600 VM2 (w=1) 9 @ 1600 VM2 (r=250, W=1) =e
a. 1600 s . VMS (w=2) - VM8 (w=2) ~~
= 1400 ee, Q 1400 VM4 (w=2) --O-- Q 1400 VM4 (w=2) »-@-
” a « VM5 (w=2 s VM5 (w=2 .
21200 Overall IOPS decrease by 20% 3 1200 (wee) B 1200 ine)
r=) ‘ F 1000 &1000
3 1000 with more VM contention 2 2vms 5 2 Minimums always met
f& 800 E B00 ° een E a ee for VM1, VM2
® 600 @ 600 Z *, 4vms @ 600 : y
© 400 a sere, Sums ® 400 Cecon®
g g setoe, °° ’ 5
< 200 < 200 ; = 200
; fs : : ! / 4 ; : :
50 100 150 200 250 300 50 100 150 200 250 300 50 100 150 200 250 300
Time (s) Time (s) Time (s)
(a) Overall array throughput (b) SFQ (D) (c) mClock with r;=300, r2=250
Figure 6: Five VMs with weights in ratio 1:1:2:2:2. VMs are started at 60 sec intervals. The overall throughput
decreases as more VMs are added. mClock enforces reservations and SFQ only does proportional allocation.
2:2:1 for the RD, OLTP, and DM workloads. First, we
ran them with zero reservations and no limits in mClock,
which is equivalent to running them with a standard fair
scheduler such as SFQ(D) [24]. The throughput and
latency achieved is shown in Figures 4(a) and (b), be-
tween times 60 and 140sec. Since RD was not fully
backlogged, and OLTP had only 16 concurrent IOs, the
work-conserving scheduler gave all the remaining queue
slots (16 of them) to the DM workload. As a result, RD
and OLTP got less than the specified proportion of IO
throughput, while DM received more. Since the device
queue was always heavily occupied by IO requests from
DM, the latency seen by RD and OLTP was higher than
desirable. We also experimented with other weight ra-
tios (which are not shown here for lack of space), but saw
no significant improvement, because the primary cause
of the poor performance seen by RD and OLTP was that
there were too many IOs from DM in the device queue.
To provide better throughput and lower latency to RD
and OLTP workloads, we changed the upper limit for
DM to 300 IOs (from unlimited) at tf = 140sec. This
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
caused the OLTP workload to see a 100% increase in
throughput and the latency was reduced by half (36 ms
to 16 ms). The RD workload also saw lower latency,
while its throughput remained equal to its demand. This
result shows that using limits with proportional sharing
can be quite effective in reducing contention for criti-
cal workloads, and this effect cannot be produced using
proportional sharing alone.
Next, we did an experiment to show that mClock ef-
fectively enforces limits in a more dynamic setting with
workloads arriving at different times. Using Iometer on
Windows Server VMs, we ran three workloads (VM1,
VM2, and VM3), each generating 16KB random reads.
We set the weights in the ratio 1:1:2, with limits of 400
IOPS on VM2 and 500 IOPS on VM3. We began with
just VM1 and a new workload was started every 60 sec-
onds. The storage device had a capacity of about 1600
random reads per second. Without the limits and based
on the weights alone, we would expect the applications
to receive 800 IOPS each when VM1 and VM2 are run-
ning, and 400, 400, and 800 IOPS respectively when
445
446
=
Average Thro
50 100 150 200 250
Time (s)
(a) SFQ(D)
VM1 (w=2) ——"
@ 1800 VM2 (w=2, |=700) ---3e--
VM3 (w=1) “4 Bee |
Gey VM4 (r=250, w=1) ---O-=
= 1400 |
21200
2 1000
VM2 gets <=700
poem WVM4 get >=250 IOPS
400
200
Average Throug
a
oO
oO
50 100 150 200 250
Time (s)
(b) mClock
Figure 7: Average throughput for VMs using SFQ(D) and mClock. mClock is able to restrict the allocation of VM2
to 700 IOPS and always provide at least 250 IOPS to VM4.
VM1, VM2, and VM3 are running together.
Figure 5 shows the throughput obtained by each of the
workloads. When we added the VM2 (at time 60sec), it
received only 400 IOPS based on its limit, and not the
800 IOPS it would have received based on the weights
alone. When we started VM3 (at time 120sec), it re-
ceived only its maximum limit, 500 IOPS, again smaller
than its throughput share based on the weights alone.
This shows that mClock is able to limit the throughput
of VMs based on specified upper limits.
4.1.2 Reservations Enforcement
To test the ability of mClock to enforce reservations, we
used a combination of 5 workloads, VM1 — VMS, all
generated using Iometer on Windows Server VMs. Each
workload maintained 32 outstanding IOs, all 16 KB ran-
dom reads, at all times. We set their shares to the ratio
1:1:2:2:2. VMI required a minimum of 300 IOPS, VM2
required 250 IOPS, and the rest had no minimum re-
quirement. To demonstrate again the working of mClock
in a dynamic environment, we began with just VM1, and
a new workload was started every 60 seconds.
Figures 6(a) shows the overall throughput observed
by the host using SFQ(D=32) and mClock. As the
number of workloads increased, the overall throughput
from the array decreased because the combined work-
load spanned larger numbers of tracks on the disks.
Figures 6(b) and (c) show the throughput obtained by
each workload using SFQ(D=32) and mClock respec-
tively. When we used SFQ(D), the throughput of each
VM decreased with increasing load, down to 160 IOPS
for VM1 and VM2, while the remaining VMs received
around 320 IOPS. In contrast, mClock provided 300
IOPS to VM1 and 250 IOPS to VM2, as desired. In-
creasing the throughput allocation also led to a smaller
latency (as expected) for VM1 and VM2, which would
not have been possible just using proportional shares.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
VM
VMI_| 4K, 75%, 100% | 0_| MAX
VM2 | 8K, 90%, 80% | 0 | 700
VM3_| 16K, 75%, 20% | _0_| MAX |
vMa MAX
Table 3: VM workloads characteristics and parameters
4.1.3. Diverse VM Workloads
In the experiments above, we used mostly homoge-
neous workloads for ease of exposition and understand-
ing. To demonstrate the effectiveness of mClock with
a non-homogeneous combination of workloads, we ex-
perimented with workloads having very different IO
characteristics. We used four workloads, generated us-
ing Iometer on Windows VMs, each keeping 32 IOs
pending at all times. The workload configurations and
the resource control settings (reservations, limits, and
weights) are shown in Table 3.
Figures 7(a) and (b) show the throughputs allocated
by SFQ(D) (weight-based allocation) and by mClock for
these workloads. mClock was able to restrict VM2 to
700 IOPS, as desired, when only two VMs were doing
IOs. Later, when VM4 became active, mClock was able
to meet the reservation of 250 IOPS for it, whereas SFQ
only provided around 190 IOPS. While meeting these
constraints, mClock was able to keep the allocation in
proportion to the weights of the VMs; for example, VM1
got twice as many IOPS as VM3 did.
We next used the same workloads to demonstrate how
an administrator may determine the reservation to use. If
the maximum latency desired and the maximum concur-
rency of the application is known, then the reservation
can be simply estimated using Little’s law as the ratio of
the concurrency to the desired latency. In our case, if it is
desired that the latency not exceed 65ms, the reservation
can be computed as 32/0.065 = 492, since the number
of concurrent IOs from each application is 32. First, we
USENIX Association
1400
OLTP1 (r=500, w=2) —+—
OLTP2 (r=500, w=1) ss
= WinVM (r=1, w=1) -=-@==
OLTP1 (r=500, w=2) —+—
OLTP2 (r=500, w=1)
m Without-mClock
1000 1000 @ With-mClock
~ 800 m 800
oa a
2 600 Psi 2 600
400 3,Reservation missed for vue 400
200 6 200
0 ‘qasssensssssssssssass5 0 r ’ r ’
0 50 100 150 200 250 0 50 100 150 200 250 VM1 (Ops/s) VM1(ms/Op) VM2 (Ops/s) VM2 (ms/Op)
Time (s) Time (s)
(a) Without mClock (b) With mClock (c)Application metrics
Figure 8: (a) Without mClock, VM2 missed its minimum requirement when WinVM started (b) With mClock, both
OLTP workloads got their reserved IOPS despite WinVM workload (c) Application-level metrics: ops/s, avg Latency
7;=512, [IOPS,ms]
VM r=, [OPS, ms]
VMI 330, 96ms 490, 68ms
VM2 390, 82ms 496, 64ms
VM3 660, 48ms 514, 64ms
VM4 665, 48ms 530, 65ms
Table 4: mClock provided low latencies to VM1 and
VM2 and throughputs close to the reservation when the
reservations were changed from r; = 1 to 512 IOPS.
ran the four VMs together with a reservation 7; = | each,
and weights in the ratio 1:1:2:2.
The throughput (IOPS) and latency received by each
in this simultaneous run are shown in Table 4. Note that
workloads received IOPS in proportion to their weights,
but the latencies of VM1 and VM2 were much higher
than desired. We then set the reservation (r;) for each
VM to be 512 IOPS; the results are shown in the last col-
umn of Table 4. Note that first two VMs received higher
IOPS of around 500 instead of 330 and 390, which is
close to their reservation targets. The latency is also
close to the expected value of 65ms. The other VMs saw
a corresponding decline in their throughput. The reser-
vation targets of VM1 and VM2 were not entirely met
because the overall throughput was slightly smaller than
the sum of reservations. This experiment demonstrates
that mClock is able to provide a strong control to stor-
age admins to meet their IOPS and latency targets for a
given VM.
4.1.4 Bursty VM Workloads
Next, we experimented with the use of idle credits given
to a workload for handling bursts. Recall that idle credits
allow a workload to receive service in a burst only if the
workload has been idle in the past and the reservations
for all VMs have been met. This ensures that if an ap-
plication is idle for a while, it gets preference when next
there is spare capacity in the system. In this experiment,
we used two workloads generated with Iometer on Win-
USENIX Association
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
VM | o=1, [IOPS, ms] | o=64, [IOPS,ms]
VM1 | 312, 49ms 316, 30.8ms
VM2 | 2420, 13.2ms 2460, 12.9ms
Table 5: The bursty workload (VM1) saw an improved
latency when given a higher idle credit of 64. The overall
throughput remained unaffected.
dows Server VMs. The first workload was bursty, gener-
ating 128 IOs every 400ms, all 4KB reads, 80% random.
The second was steady, producing 16 KB reads, 20% of
them random and the rest sequential, with 32 outstand-
ing IOs. Both VMs had equal shares, no reservation, and
no limit imposed on the throughput. We used idle-credit
(oO) values of 1 and 64 for our experiment.
Table 5 shows the IOPS and average latency obtained
by the bursty VM for the two settings of the idle credit.
The number of IOPS were almost equal in either case
because idle credits do not impact the overall bandwidth
allocation over time, and VMI had a bounded request
rate. VM2 also saw almost the same IOPS for the two
settings of idle credits. However, we notice that the la-
tency seen by the bursty VM1 decreased as we increased
the idle credits. VM2 also saw a similar or a slightly
smaller latency, perhaps due to the increase in efficiency
of doing several IOs at a time from a single VM, which
are likely to be spatially closer on the storage device.
In the extreme, however, a very high setting of idle
credits can lead to high latencies for non-bursty work-
loads by distorting the effect of the weights (although
not the reservations or limits), and so we limit the set-
ting to a maximum of 256 IOs in our implementation.
This result indicates that using idle credits is an effec-
tive mechanism to help lower the latency of bursts.
4.1.5 Filebench Workloads
To test mClock with more realistic workloads, we ex-
perimented with two Linux RHEL VMs running OLTP
workload using Filebench [26]. Each VMs was config-
447
448
ured with 1 VCPU, 512 MB of RAM, 10GB database
disk, and | GB log virtual disk. To introduce throughput
fluctuation another Windows 2003 VM running Iometer
was used. The Iometer workload produced 32 concur-
rent, 16KB random reads. We assigned the weights in
the ratio 2:1:1 to the two OLTP workloads and the Iome-
ter workload, respectively, and gave a reservation of
500 IOPS to each OLTP workload. We initially started
the two OLTP workloads together and then the Iometer
workload at t = 115s.
Figures 8(a) and (b) show the IOPS received by the
three workloads as measured inside the hypervisor, with
and without mClock. Without mClock, as soon as the
Iometer workload started, OLTP2 started missing its
reservation and received around 250 IOPS. When run
with mClock, both the OLTP workloads were able to
achieve their reservations of 500 IOPS. This shows that
mClock can protect critical workloads from a sudden
change in the available throughput. The application-
level metrics — the number of operations/sec and the
transaction latency reported by Filebench — are sum-
marized in Figure 8(c). Note that mClock was able to
provide higher operations/sec and lower latency per op-
eration in OLTP VMs, even with an increase in the over-
all IO contention.
4.2 dmClock Evaluation
In this section, we present results of a dmClock imple-
mentation in a distributed storage system. The system
consisted of multiple storage servers (nodes) — three in
our experiment. Each node was implemented using a
virtual machine running RHEL Linux with a 1OGB OS
disk and a 1OGB experimental disk, from which the data
was served. Each experimental disk was placed on a
different LUN backed by RAID-5 group with six disks.
Thus, each experimental disk could do roughly 1500
IOPS for a random workload. A single storage device
shared by all clients, was then constructed by striping
across all the storage nodes. This configuration repre-
sents a clustered-storage system where there are multi-
ple storage nodes, each with dedicated LUNs used for
servicing IOs.
We implemented dmClock as a user-space module in
each server node. The module receives IO requests
containing IO size, offset, type (read/write), the 6 and
p parameters, and data in the case of write requests.
The module can keep up to 16 outstanding IOs (using
16 threads) to execute the requests, and the requests
are scheduled on these threads using the dmClock algo-
rithm. The clients were run on a separate physical ma-
chine. Each client generated an IO workload for one or
more storage nodes and also acted as a gateway, piggy-
backing the 5 and p values onto each request sent to
the storage nodes. Each client workload consisted of
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
3000
[J constraint-based
2500 Hl Weight-based
& 2000
9
2 1500
el
a
3
£ 1000
E
500 |
° H C2 C3
(a)R=[1,1,1] bya ‘1800-1000 100]
Figure 9: IOPS obtained by the three clients for two dif-
ferent cases. (a) All clients accessed the servers uni-
formly, with no reservations. (b) Clients had reserva-
tions of 800, 1000, and 100 IOPS, respectively.
2500
2000
1500
1000
500 | Client 1 (r=800, ‘i
lient 2(r=1000, o hod Senne
Average Throughput (IOPS)
20 40 60 80 100
Time (s)
Figure 10: IOPS obtained by the two clients. When c
was started, c, still met its reservation target.
8KB random reads with 64 concurrent IOs, uniformly
distributed over the nodes it used. We used our own
workload generator here because of the need to add ap-
propriate 6 and p values to each request.
In first experiment, we used three clients, {c1,c2,c3},
each accessing all three storage nodes. The weights were
set in the ratio 1:4:6, with no upper limit on the IOPS.
We experimented with two different cases: (1) No reser-
vation per client, (2) Reservations of 800, 1000 and 100
for clients {c,,c2,c3} respectively. These values were
used to highlight a use case where the allocation based
on reservations may be higher than the allocation based
on weights or shares for some clients. The output for
these two cases is shown in Figure 9 (a) and (b). Case
(a) shows the overall IO throughput obtained by three
clients without reservations. As expected, each client
received total service in proportion to its weight. In case
(b), dmClock was able to meet the reservation goal of
800 IOPS for cj, which would have been missed with
a proportional share scheduler. The remaining through-
put was divided between clients cz and c3 in the ratio
2:3 as they respectively received around 1750 and 2700
USENIX Association
USENIX Association
IOPS. Figure 9(b) also shows the IOs done during the
two phases of the algorithm.
Next, we experimented with non-uniform accesses
from clients. In this case we used two clients c,,c2 and
two storage servers. The reservations were set to 800
and 1000 IOPS and the weights were again in the ra-
tio 1:4. c; sent IOs to the first storage node (S;) only
and we started cz after approximately 40 seconds. Fig-
ure 10 shows the IOPS obtained by the two clients with
time. Initially, c; got the full capacity from server S; and
when C2 was started, c; was still able to get an allocation
close to its reservation of 800 IOPS. The remaining ca-
pacity was allocated to c2, which received around 1400
IOPS. A distributed weight-proportional scheduler [45]
would have given approximately 440 IOPS to c; and the
remainder to cz, which would have missed the minimum
requirement of cj. This shows that even when the ac-
cess pattern is non-uniform in a distributed environment,
dmClock is able to meet reservations and assign overall
IOPS in the ratio of weights to the extent possible.
5 Conclusions
In this paper, we presented a novel IO scheduling algo-
rithm, mClock, that provides per-VM quality of service
in presence of variable overall throughput. The QoS re-
quirements for a VM are expressed as a minimum reser-
vation, a maximum limit, and a proportional share. A
key aspect of mClock is its ability to enforce such con-
trols even with fluctuating overall capacity, as shown by
our implementation in the VMware ESX server hypervi-
sor. We also presented dmClock, a distributed version of
our algorithm that can be used in clustered storage sys-
tem architectures. We implemented dmClock in a dis-
tributed storage environment and showed that it works
as specified, maintaining global per-client reservations,
limits, and proportional shares, even though the sched-
ulers run locally on the storage nodes.
The controls provided by mClock should allow
stronger isolation between VMs. Although we have
shown the effectiveness for hypervisor IO scheduling,
we believe that the techniques are quite generic and can
be applied to array-level scheduling and to other re-
sources such as network bandwidth allocation as well.
In our future work, we plan to explore further how to set
these parameters to meet application-level SLAs.
6 Acknowledgement
We would like to thank our shepherd, Jon Howell, and
the anonymous reviewers for their comments, which
helped improve this paper. We thank Carl Waldspurger
for valuable discussions and feedback on this work.
Many thanks to Chethan Kumar for providing us with
motivational use cases and Ganesha Shanmuganathan
for discussions on the algorithm. Part of the work
was done while the first author was a PhD student at
Rice University [19]. The support of the National Sci-
ence Foundation under Grants CNS-0541369 and CNS-
0917157 is gratefully acknowledged. A preliminary ver-
sion of the dmClock algorithm appeared as a brief an-
nouncement in PODC 2007 [21].
References
[1] Personal communications with many customers.
[2] Dell Inc. DVDStore benchmark. Heep?
//delltechcenter.com/page/DVD+store.
[3] Distributed Resource Scheduler, VMware Inc.
http://www.vmware.com/products/vi/
vc/drs.html.
[4] HP Lefthand SAN appliance.
lefthandsan.com/.
http://www.
[5] Iometer. http: //www.iometer.org.
[6] Seanodes Inc. http://www. seanodes.com/.
[7] VMware ESX Server User Manual, December 2007.
VMware Inc.
[8] vSphere Resource Management Guide, December 2009.
VMware Inc.
[9] M. Abd-El-Malek et al. Ursa Minor: Versatile cluster-
based storage. In USENIX FAST, 2005.
[10] J.C. R. Bennett and H. Zhang. WFQ: Worst-case fair
weighted fair queueing. In IVFOCOM, pages 120-128,
1996.
{11] A. T. Clements, I. Ahmad, M. Vilayannur, J. Li, and
V. Inc. Decentralized deduplication in SAN cluster
file systems. In USENIX Annual Technical Conference,
2009.
[12] R.L. Cruz. Quality of service guarantees in virtual circuit
switched networks. [EEE Journal on Selected Areas in
Communications, 13(6):1048-1056, 1995.
[13] A. Demers, S. Keshav, and S. Shenker. Analysis and sim-
ulation of a fair queuing algorithm. Journal of Internet-
working Research and Experience, 1(1):3-—26, September
1990.
[14] K. J. Duda and D. R. Cheriton. Borrowed-virtual-time
(BVT) scheduling: supporting latency-sensitive threads
in a general-purpose scheduler. SOSP:ACM Symposium
on Operating Systems Principles, 1999.
[15] S. Golestani. A self-clocked fair queueing scheme for
broadband applications. In INFOCOMM’94, pages 636-—
646, April 1994.
[16] K. Govil, D. Teodosiu, Y. Huang, and M. Rosenblum.
Cellular disco: resource management using virtual clus-
ters on shared-memory multiprocessors. In SOSP:ACM
Symposium on Operating Systems Principles, 1999.
[17] P. Goyal, X. Guo, and H. M. Vin. A hierarchial CPU
scheduler for multimedia operating systems. SIGOPS
Oper. Syst. Rev., 30(SD):107—121, 1996.
[18] P. Goyal, H. M. Vin, and H. Cheng. Start-Time Fair
Queuing: A scheduling algorithm for integrated services
packet switching networks. Technical Report CS-TR-96-
02, UT Austin, January 1996.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) 449
450
[19]
[20]
[21]
[22]
[23]
[24]
[25]
[26]
[27]
[28]
[29]
[30]
[31]
[32]
[33]
[34]
A. Gulati. Performance virtualization and QoS in Shared
storage systems. PhD thesis, Rice University, Houston,
TX, USA, 2008.
A. Gulati, I. Ahmad, and C. Waldspurger. PARDA: Pro-
portional Allocation of Resources in Distributed Storage
Access. In (FAST ’09)Proceedings of the Seventh Usenix
Conference on File and Storage Technologies, Feb 2009.
A. Gulati, A. Merchant, and P. Varman. d-clock: Dis-
tributed QoS in heterogeneous resource environments. In
PODC ’07: Proceedings of the twenty-sixth annual ACM
symposium on Principles of distributed computing, pages
330-331, New York, NY, USA, 2007. ACM.
A. Gulati, A. Merchant, and P. Varman. pClock: An ar-
rival curve based approach for QoS in shared storage sys-
tems. In ACM SIGMETRICS, 2007.
L. Huang, G. Peng, and T. cker Chiueh. Multi-
dimensional storage virtualization. In ACM SIGMET-
RICS, pages 14-24, 2004.
W. Jin, J. S. Chase, and J. Kaur. Interposed proportional
sharing for a storage service utility. In ACM SIGMET-
RICS, 2004.
M. B. Jones, D. Rosu, and M.-C. Rosu. CPU reservations
and time constraints: Efficient, predictable scheduling of
independent activities. In SOSP:ACM Symposium on Op-
erating Systems Principles, 1997.
R. McDougall. Filebench: Application level file system
benchmark. http://www.solarisinternals.
com/si/tools/filebench/index.php.
T. S. E. Ng, D. C. Stephens, I. Stoica, and H. Zhang.
Supporting best-effort traffic with fair service curve. In
Measurement and Modeling of Computer Systems, pages
218-219, 1999.
J. Nieh and M. S. Lam. A smart scheduler for multimedia
applications. ACM Trans. Comput. Syst., 21(2):117-163,
2003.
A. K. Parekh and R. G. Gallager. A generalized processor
sharing approach to flow control in integrated services
networks: the single-node case. IEEE/ACM Trans. Netw.,
1(3):344-357, 1993.
Y. Saito et al. FAB: building distributed enterprise disk
arrays from commodity components. SIGPLAN Not.,
39(11):48-58, 2004.
H. Sariowan, R. L. Cruz, and G. C. Polyzos. Schedul-
ing for quality of service guarantees via service curves.
In Proceedings of the International Conference on Com-
puter Communications and Networks, pages 512-520,
1995.
R. Stanojevic and R. Shorten. Fully decentralized emula-
tion of best-effort and processor sharing queues. In ACM
SIGMETRICS, 2008.
D. Stiliadis and A. Varma. Latency-rate servers: a gen-
eral model for analysis of traffic scheduling algorithms.
IEEE/ACM Transactions on Networking, 6(5):611-624,
1998.
I. Stoica, H. Abdel-wahab, and K. Jeffay. On the duality
between resource reservation and proportional share re-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
[35]
[36]
[37]
[38]
[39]
[40]
[41]
[42]
[43]
[44]
[45]
[46]
[47]
[48]
[49]
[50]
source allocation. In Proc. of Multimedia Computing and
Networking, pages 207-214, 1997.
I. Stoica, H. Abdel-wahab, K. Jeffay, S. K. Baruah, J. E.
Gehrke, and C. G. Plaxton. A proportional share resource
allocation algorithm for real-time, time-shared systems.
In Proceedings of the 17th IEEE Real-Time Systems Sym-
posium, pages 288-299, 1996.
I. Stoica, H. Zhang, and T. S. E. Ng. A hierarchical
fair service curve algorithm for link-sharing, real-time,
and priority services. LEEE/ACM Trans. Netw., 8(2):185—
199, 2000.
D. G. Sullivan and M. I. Seltzer. Isolation with Flex-
ibility: A resource management framework for central
servers. In USENIX Annual Technical Conference, 2000.
S. Suri, G. Varghese, and G. Chandramenon. Leap for-
ward virtual clock: A new fair queueing scheme with
guaranteed delay and throughput fairness. In INFO-
COMM ’97, April 1997.
B. Verghese, A. Gupta, and M. Rosenblum. Performance
isolation: sharing and isolation in shared-memory multi-
processors. In ASPLOS-VIII, pages 181-192, New York,
NY, USA, 1998. ACM.
VMware, Inc. Introduction to VMware Infrastructure.
2007. http://www.vmware.com/support/pubs/.
M. Wachs, M. Abd-El-Malek, E. Thereska, and G. R.
Ganger. Argon: performance insulation for shared stor-
age servers. In USENIX FAST, Berkeley, CA, USA, 2007.
C. Waldspurger. Personal Communications.
C. A. Waldspurger. Lottery and stride scheduling: flex-
ible proportional-share resource management. PhD the-
sis, Massachusetts Institute of Technology, Cambridge,
MA, USA, 1995.
C. A. Waldspurger. Memory resource management in
VMware ESX server. In (OSDI’02): Proceedings of the
Fifth symposium on Operating systems Design and Im-
plementation, 2002.
Y. Wang and A. Merchant. Proportional-share scheduling
for distributed storage systems. In Usenix FAST, Febru-
ary 2007.
W. Wilcke et al. IBM intelligent bricks project —
petabytes and beyond. JBM Journal of Research and De-
velopment, 50, 2006.
J. C. Wu, S. Banachowski, and S. A. Brandt. Hierarchi-
cal disk sharing for multimedia systems. In NOSSDAV.
ACM, 2005.
J.C. Wu and S. A. Brandt. The design and implementa-
tion of Aqua: an adaptive quality of service aware object-
based storage device. In Proc. of IEEE/NASA MSST,
pages 209-218, May 2006.
J. Zhang, A. Sivasubramaniam, Q. Wang, A. Riska,
and E. Riedel. Storage performance virtualization via
throughput and latency control. In JEEE MASCOTS,
pages 135-142, 2005.
L. Zhang. VirtualClock: A new traffic control algo-
rithm for packet-switched networks. ACM Trans. Com-
put. Syst., 9(2):101-124.
USENIX Association
USENIX Association
Virtualize Everything but Time
Timothy Broomhead
Laurence Cremean
Julien Ridoux Darryl Veitch
Center for Ultra-Broadband Information Networks (CUBIN)
Department of Electrical & Electronic Engineering, The University of Melbourne, Australia
{t.broomhead, |.cremean} @ugrad.unimelb.edu.au, {jridoux, dveitch} @ unimelb.edu.au
Abstract
We propose a new timekeeping architecture for virtu-
alized systems, in the context of Xen. Built upon a feed-
forward based RADclock synchronization algorithm, it
ensures that the clocks in each OS sharing the hardware
derive from a single central clock in a resource effective
way, and that this clock is both accurate and robust. A
key advantage is simple, seamless VM migration with
consistent time. In contrast, the current Xen approach
for timekeeping behaves very poorly under live migra-
tion, posing a major problem for applications such as fi-
nancial transactions, gaming, and network measurement,
which are critically dependent on reliable timekeeping.
We also provide a detailed examination of the HPET and
Xen Clocksource counters. Results are validated using a
hardware-supported testbed.
1 Introduction
Virtualization represents a major movement in the evo-
lution of computer infrastructure. Its many benefits in-
clude allowing the consolidation of server infrastructure
onto fewer hardware platforms, resulting in easier man-
agement and energy savings. Virtualization enables the
seamless migration of running guest operating systems
(guest OSs), which reduces reliance on dedicated hard-
ware, and eases maintenance and failure recovery.
Timekeeping is a core service on computing plat-
forms, and accurate and reliable timekeeping is im-
portant in many contexts including network measure-
ment and high-speed trading in finance. Other applica-
tions where accurate timing is essential to maintain at
all times, and where virtualization can be expected to
be used either now or in the future, include distributed
databases, financial transactions, and gaming servers.
The emerging market of outsourced cloud computing
also requires accurate timing to manage and correctly bill
customers using virtualized systems.
Software clocks are based on local hardware (oscilla-
tors), corrected using synchronization algorithms com-
municating with reference clocks. For cost and conve-
nience reasons, reference clocks are queried over a net-
work.
Since a notion of universally shared absolute time is
tied to physics, timekeeping poses particular problems
for virtualization, as a tight ‘real’ connection must be
maintained across the OSs sharing the hardware. Both
timekeeping and timestamping rely heavily on hardware
counters. Virtualization adds an extra layer between the
hardware and the OSs, which creates additional resource
contention, and increased latencies, that impact perfor-
mance.
In this paper we propose a new timekeeping architec-
ture for para-virtualized systems, in the context of Xen
[1]. Using a hardware-supported testbed, we show how
the current approach using the Network Time Protocol
(NTP) system is inadequate, in particular for VM migra-
tion. We explain how the feed-forward based synchro-
nization adopted by the RADclock [14] allows a depen-
dent clock paradigm to be used and ensures that all OSs
sharing the hardware share the same (accurate and ro-
bust) clock in a resource effective way. This results in
robust and seamless live migration because each phys-
ical host machine has its own unique clock, with hard-
ware specific state, which never migrates. Only a state-
less clock-reading function migrates. We also provide a
detailed examination and comparison of the HPET and
Xen Clocksource counters.
Neither the idea of a dependent clock, nor the RAD-
clock algorithm, are new. The key contribution here is to
show how the feed-forward nature, and stateless clock
read function, employed by the RADclock, are ideally
suited to make the dependent clock approach actually
work. In what is the first evaluation of RADclock in a vir-
tualized context, we show in detail that the resulting so-
lution is orders of magnitude better than the current state
of the art in terms of both average and peak error follow-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI’10) 451
452
ing disruptive events, in particular live migration. We
have integrated our work into the RADclock [14] pack-
ages for Linux, which now support the architecture for
Xen described here.
After providing necessary background in Section 2,
we motivate our work in depth by demonstrating the in-
adequacies of the status quo in Section 3. Since hardware
counters are key to timekeeping in general and to our so-
lution in particular, Section 4 provides a detailed exam-
ination of the behavior of counters of key importance to
Xen. Section 5 describes and evaluates our proposed tim-
ing architecture on a single physical host, and Section 6
deals with migration. We conclude in Section 7.
2 Background
We provide background on Xen, hardware counters,
timekeeping, the RADclock and NTP clocks, and com-
parison methodology.
To the best of our knowledge there is no directly rel-
evant peer-reviewed published work on timekeeping in
virtualized systems. A valuable resource however is [21].
2.1 Para-Virtualization and Xen
All virtualization techniques rely on a hypervisor, which,
in the case of Xen [1, 2, 9], is a minimal kernel with
exclusive access to hardware devices. The hypervisor
provides a layer of abstraction from physical hardware,
and manages physical resources on behalf of the guests,
ensuring isolation between them. We work within the
para-virtualization paradigm, whereby all guest OS’s are
modified to have awareness of, and access to, the native
hardware via hypercalls to the hypervisor, which are sim-
ilar to a system call. It is more challenging to support
accurate timing under the alternative fully hardware vir-
tualized paradigm [2], and we do not consider this here.
Although we work in the context of para-virtualized
Xen, the architecture we propose has broader applicabil-
ity. We focus on Linux OS’s as this is the most active
platform for Xen currently. In Xen, the guest OSs be-
long to two distinct categories: Dom0 and DomU. The
former is a privileged system which has access to most
hardware devices and provides virtual block and network
devices for the other, DomU, guests.
2.2 Hardware Counters
The heart of any software clock is local oscillator hard-
ware, accessed via dedicated counters. Counters com-
monly available today include the Time Stamp Counter
(TSC) [6] which counts CPU cycles!, the Advanced Con-
ITSC is x86 terminology, other architectures use other names.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
figuration and Power Interface (ACPI) [10], and the
High Precision Event Timer (HPET) [5].
The TSC enjoys high resolution and also very fast ac-
cess. Care is however needed in architectures where a
unique TSC of constant nominal frequency may not ex-
ist. This can occur because of multiple processors with
unsynchronized TSC’s, and/or power management ef-
fects resulting in stepped frequency changes and execu-
tion interruption. Such problems were endemic in archi-
tectures such as Intel Pentium III and IV, but have been
resolved in recent architectures such as Intel Nehalem
and AMD Barcelona.
In contrast to CPU counters like the TSC, HPET and
ACPI are system-wide counters which are unaffected by
processor speed issues. They are always on and run at
constant nominal rate, unless the entire system is sus-
pended, which we ignore here. HPET is accessed via
a data bus and so has much slower access time than the
TSC, as well as lower resolution as its nominal frequency
is about 14.3MHz. ACPI has even lower resolution with
a frequency of only 3.57MHz. It has even slower access,
since it is also read via a bus but unlike HPET is not
memory mapped.
Beyond the counter ticking itself, power management
affects all counters through its impact on counter access
latency, which naturally requires CPU instructions to be
executed. Recent processors can, without stopping exe-
cution, move between different P-States where the oper-
ating frequency and/or voltage are varied to reduce en-
ergy consumption. Another strategy is to stop processor
execution. Different such idle states, or C-States CO, Cl,
C2... are defined, where CO is normal execution, and the
deeper the state, the greater the latency penalty to wake
from it [12]. The impact on latency of these strategies is
discussed in more detail later.
2.3 Xen Clocksource
The Xen Clocksource is a hardware/software hybrid
counter presented to guest OSs by the hypervisor. It
aims to combine the reliability of a given platform timer
(HPET here) with the low access latency of the TSC.
It is based on using the TSC to interpolate between
HPET readings made on ‘ticks’ of the periodic inter-
rupt scheduling cycle of the OS (whose period is typi-
cally Ims), and is scaled to a frequency of approximately
1GHz. It is a 64-bit cumulative counter, and is effectively
initialized to zero for each guest when they boot (this is
implemented by a ‘system time’ variable they keep) and
monotonically increases.
The Xen Clocksource interpolation is a relatively
complex mechanism that accounts for lost TSC ticks
(it actively overwrites the TSC register) and frequency
changes of the TSC due to power management (it main-
USENIX Association
USENIX Association
tains a TSC scaling factor which can be used by guests to
scale their TSC readings). Such compensation is needed
on some older processors as described in Section 2.2.
2.4 Clock Fundamentals
A distinction must be drawn between the software clock
itself, and timestamping. A clock may be perfect, yet
timestamps made with it be very inaccurate due to large
and/or variable access latency. Such timestamping er-
rors will vary widely depending on context and may er-
roneously reflect on the clock itself.
By a raw timestamp we mean a reading of the underly-
ing counter. For a clock C' which reads C(t) at true time
t, the final timestamp will be a time in seconds based not
only on the raw timestamp, but also the clock parameters
set by the synchronization algorithm.
A local (scaled) counter is not a suitable clock and
needs to be synchronized because all counters drift if left
to themselves: their rate, although very close to constant
(typically measured to 1 part in 10° or less), varies. Drift
is primarily influenced by temperature.
Remote clock synchronization over a network is based
on a (typically bidirectional) exchange of timing mes-
sages from an OS to a time server and back, giving rise to
four timestamps: two made by the OS as the timing mes-
sage (here an NTP packet) leaves then returns, and two
made remotely by the time server. Typically, exchanges
are made periodically: once every poll-period.
There are two key problems faced by remote synchro-
nization. The first is to filter out the variability in the
delays to and from the server, which effectively corrupt
timestamps. This is the job of the clock synchronization
algorithm, and it is judged on its ability to do this well
(small error and small error variability) and consistently
in real environments (robustness).
The second problem is that of a fundamental ambigu-
ity between clock error and the degree of path asymme-
try. Let A = d! — d! denote the true path asymmetry,
where d' and d! are the true minimum one-way delays
to and from the server, respectively; and let r = d! + dt‘
be the minimal Round Trip Time (RTT). In the absence
of any external side-information on A, we must guess a
value, and A = 0 is typically chosen, corresponding to
a symmetric path. This allows the clock to be synchro-
nized, but only up to an unknown additive error lying
somewhere in the range [—1,7]. This ambiguity cannot
be circumvented, even in principle, by any algorithm.
We explore this further under Experimental Methodol-
ogy below.
2.5 Synchronization Algorithms
The ntpd daemon [11] is the standard clock synchro-
nization algorithm used today. It is a feedback based
design, in particular since system clock timestamps are
used to timestamp the timing packets. The existing ker-
nel system clock, which provides the interface for user
and kernel timestamping and which is ntpd-oriented, is
disciplined by ntpd. The final software clock is therefore
quite a complex system as the system clock has its own
dynamics, which interacts with that of ntpd via feedback.
On Xen, ntpd relies on the Xen Clocksource as its under-
lying counter.
The RADclock [20] (Robust Absolute and Difference
Clock) is a recently proposed alternative clock synchro-
nization algorithm based on a feed-forward design. Here
timing packets are timestamped using raw packet times-
tamps. The clock error is then estimated based on these
and the server timestamps, and subtracted out when the
clock is read. This is a feed-forward approach, since er-
rors are corrected based on post-processing outputs, and
these are not themselves fed back into the next round of
inputs. In other words, the raw timestamps are indepen-
dent of clock state. The ‘system clock’ is now stateless,
simply returning a function of parameters maintained by
the algorithm.
More concretely, the (absolute) RADclock is defined
as C,(t) = N(t)-p+ K — E(t), where N(t) is the raw
timestamp made at true time ¢, p is a stable estimate of
average counter period, K is a constant which aligns the
origin to the required timescale (such as UTC), and E(t)
is the current estimate of the error of the ‘uncorrected
clock’ N(t)--+ K which is removed when the clock
is read. The parameters p, K and F are maintained by
the clock algorithm (see [20] for details of the algorithm
itself). The clock reading function simply reads (or is
passed) the raw timestamp NV for the event of interest,
fetches the clock parameters, and returns C;,(t).
The RADclock can use any counter which satisfies ba-
sic requirements, namely that it be cumulative, does not
roll over, and has reasonable stability. In this paper we
provide results using both the HPET and Xen Clock-
source.
2.6 Experimental Methodology
We give a brief description of the main elements of our
methodology for evaluating clock performance. More
details can be found in [15].
The basic setup is shown in Figure 1. It incorporates
our own Stratum-1 NTP server on the LAN as the ref-
erence clock, synchronised via a GPS-corrected atomic
clock. NTP timing packets flow between the server and
the clocks in the host machine (two OS’s each with two
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) 453
454
clocks are shown in the figure) for synchronization pur-
poses, typically with a poll period of 16 seconds. For
evaluation purposes, a separate box sends and receives
a flow of UDP packets (with period 2 seconds) to the
host, acting as a set of timestamping ‘opportunities’ for
the clocks under test. In this paper a separate stream was
sent to each OS in the host, but for a given OS, all clocks
timestamp the same UDP flow.
Based on timestamps of the arrival and departure of
the UDP packets, the testbed allows two kinds of com-
parisons.
External: a specialized packet stamping ‘DAG’ card [4]
timestamps packets just before they enter the NIC of the
host machine. These can be compared to the timestamps
for the same packets taken by the clocks inside the host.
The advantage is an independent assessment; the disad-
vantage is that there is ‘system’ lying between the two
timestamping events, which adds a ‘system noise’ to the
error measurement.
Internal: clocks inside the same OS timestamp the pack-
ets back-to-back (thanks to our kernel modifications), so
subtracting these allows the clocks to be compared. The
advantage is the elimination of the system noise between
the timestamps; the disadvantage is that differences be-
tween the clocks cannot be attributed to any specific
clock.
The results appearing in this paper all use the external
comparison, but internal comparisons were also used as
a key tool in the process of investigation and validation.
As far as possible experiments are run concurrently so
that clocks to be compared experience close to identical
conditions. For example, clocks in the same OS share
the very same NTP packets to the time server (and hence
in particular, share the same poll period). There are a
number of subtle issues we have addressed regarding the
equivalence between what the test UDP packets, and the
NTP packets actually used by the algorithm, ‘see’, which
depends on details of the relative timestamping locations
in the kernel. This topic is discussed further below in
relation to ‘host asymmetry’.
; : GPS
Internal Monitor External Monitor Receiver
Atomic
Host 2 SoZ ooo |": = | =
_ g o 1
3 RADclock 0 ;
5 ye
S S Snipe NTP f =) |
ao = q L L. Unix PC SW-GPS
oo “fm
a EC "Rabe tay Bone NTP Server
| g JRADclock |= Hub & Receiver Stratum 4
eblchinlty PPS Sync. ——NTP flow ooo UDP flow y Timestamping
Figure |: Testbed and clock comparison methodology.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
It is essential to note that the delays experienced by
timing packets have components both in the network, and
in the host itself (namely the NIC + hardware + OS),
each with their own minimum RTT and asymmetry val-
ues. Whereas the DAG card timestamps enable the net-
work side to be independently measured and corrected,
the same is not true of the host side component. Even
when using the same server then, a comparison of dif-
ferent clocks, which have different asymmetry induced
errors, is problematic. Although the spread of errors can
be meaningfully compared, the median errors can only
be compared up to some limit imposed by the (good but
not perfect) methodology, which is of the order of | to
10 us. Despite these limitations, we believe our method-
ology to be the best available at this time.
3 Inadequacy of the Status-Quo
The current timekeeping solution for Xen is built on top
of the ntpd daemon. The single most important thing
then regarding the performance of Xen timekeeping is to
understand the behavior first of ntpd in general, and then
in the virtualized environment.
There is no doubt that ntpd can perform well under
the right conditions. If a good quality nearby time server
is available, and if ntpd is well configured, then its per-
formance on modern kernels is typically in the tens of
microseconds range and can rival that of the RADclock.
An example in the Xen context is provided in Figure 2,
where the server is a Stratum-1 on the same LAN, and
both RADclock and ntpd, running on Dom0O in paral-
lel, are synchronizing to it using the same stream of
NTP packets. Here we use the host machine kultarr, a
2.13GHz Intel Core 2 Duo. Xen selects a single CPU for
use with Xen Clocksource, which is then used by ntpd.
Power management is disabled in the BIOS.
The errors show a similar spread, with an Inter-
Quartile Range (IQR) of around 10 us for each. Note that
here path asymmetry effects have not been accounted for,
so that as discussed above the median errors do not re-
flect the exact median error for either clock.
In this paper our focus is on the right architecture
for timing in virtualized systems, in particular such that
seamless VM migration becomes simple and reliable,
and not any performance appraisal of ntpd per se. Ac-
cordingly, unless stated otherwise we consistently adopt
the configuration which maximizes ntpd performance —
single nearby statically allocated Stratum-1 server, static
and small polling period.
The problem with ntpd is the sudden performance
degradations which can occur when conditions deviate
from ‘ideal’. We have detailed these robustness issues of
ntpd in prior work, including [17, 18]. Simply put, when
path delay variability exceeds some threshold, which is a
USENIX Association
USENIX Association
]
S
a
Oo
Clock error [us
nA
oS
~—ntpd
RADclock
1) Aa DA mn
RADclock
Med: 33.3
IQR: 10.9
oO
oO
oa
10
Time [day]
60
20 30 40 30 40 50
Clock Error [us] Clock Error [Ls]
Figure 2: RADclock and ntpd uncorrected performance on dom0, measured using the external comparison with DAG.
4000
2000;
I
to
So
So
Oo oO
Clock error [us]
-4000/
~—ntpd dependent
1.2 1.4 1.6
0 0.2 0.4 0.6 0.8 1
Time [Hours]
Figure 3: Error in the DomU dependent clock (dependent
on the ntpd on Dom0 shown in Figure 2), measured using
the external comparison with DAG. This 2 hour zoom is
representative of an experiment 20 days long.
complex function of parameters, stability of the feedback
control is lost, resulting in errors which can be large over
small to very long periods. Recovery from such periods
is also subject to long convergence times.
Consider now the pitfalls of using ntpd for timekeep-
ing in Xen, through the following three scenarios.
Example 1 - Dependent nitpd clock In a dependent
clock paradigm, only Dom0 runs a full clock synchro-
nization algorithm, in this case ntpd. Here we use a
2.6.26 kernel, the last one supporting ntpd dependent
timekeeping.
In the solution detailed in [19], synchronizing times-
tamps from ntpd are communicated to DomU guests via
the periodic adjustment of a ‘boot time’ variable in the
hypervisor. Timestamping in DomU is achieved by a
modified system clock call which uses Xen Clocksource
to extrapolate from the last time this variable was up-
dated forward to the current time. The extrapolation as-
sumes Xen Clocksource to be exactly 1GHz, resulting
in a sawtooth shaped clock error which, in the example
from our testbed given in Figure 3, is in the millisecond
range. This is despite the fact that the Dom0 clock it
derives from is the one depicted in Figure 2, which has
excellent performance in the 10 us range.
These large errors are ultimately a result of the way
in which ntpd interacts with the system clock. With no
ntpd running on DomU (the whole point of the depen-
dent clock approach), the system clock has no way of in-
telligently correcting the drift of the underlying counter,
in this case Xen Clocksource. The fact that Xen Clock-
source is in reality only very approximately 1GHz means
that this drift is rapid, indeed appearing to first order as
a simple skew, that is a constant error in frequency. This
failure of the dependent clock approach using ntpd has
led to the alternative solution used today, where each
guest runs its own independent ntpd daemon.
Example 2 — Independent nipd clock In an inde-
pendent clock paradigm, which is used currently in Xen
timekeeping, each guest OS (both Dom0 and DomU) in-
dependently runs its own synchronization algorithm, in
this case ntpd, which connects to its own server using its
own flow of NTP timing packets. Clearly this solution
is not ideal in terms of the frugal use of server, network,
NIC and host resources. In terms of performance, the un-
derlying issue is that the additional latencies suffered by
guests in the virtual context make it more likely ntpd will
be pushed into instability. Important examples of such
latencies are the descheduling and time-multiplexing of
guests across physical cores.
An example is given in Figure 4, where, despite syn-
chronizing to the same high quality server on the LAN as
before, stability is lost and errors reach the multiple mil-
lisecond range. This was brought about simply by adding
a moderate amount of system load (some light churn of
DomU guests and some moderate CPU activity on other
guests), and allowing NTP to select its own polling pe-
riod (in fact the default configuration), rather than fixing
it to a constant value.
a
So
oS
oO
Clock error [us]
2 4 6 8 10 12 14 16
Time [Hours]
I
a
So
oS
co?
Figure 4: Error in the ntpd independent clock on DomU
synchronizing to a Stratum-1 server on the LAN, with
polling period set by ntpd. Additional guests are created
and destroyed over time.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) = 455
456
Example 3 — Migrating independent ntpd clock This
example considers the impact of migration on the syn-
chronization of the ntpd independent clock (the current
solution) of a migrating guest. Since migration is treated
in detail in Section 6, we restrict ourselves here to point-
ing to Figure 11, where extreme disruption — of the order
of seconds — is seen following migration events. This is
not a function of this particular example but is a generic
result of the design of ntpd in conjunction with the in-
dependent clock paradigm. A dependent ntpd clock so-
lution would not exhibit such behavior under migration,
however it suffers from other problems as detailed above.
In summary, there are compelling design and robust-
ness reasons for why ntpd is ill suited to timekeeping in
virtual environments. The RADclock solution does not
suffer from any of the drawbacks detailed above. It is
highly robust to disruptions in general as described for
example in [20, 15, 16], is naturally suited to a depen-
dent clock paradigm as detailed in Section 5, as well as
to migration (Section 6).
4 Performance of Xen Clocksource
The Xen Clocksource hybrid counter is a central compo-
nent of the current timing solution under Xen. In this sec-
tion we examine its access latency under different condi-
tions. We also compare it to that of HPET, both because
HPET is a core component of Xen Clocksource, so this
enables us to better understand how the latter is perform-
ing, and because HPET is a good choice of counter, be-
ing widely available and uninfluenced by power manage-
ment. This section also provides the detailed background
necessary for subsequent discussions on network noise.
Access latency, which impacts directly on timekeep-
ing, depends on the access mechanism. Since the tim-
ing architecture we propose in Section 5 is based on a
feed-forward paradigm, to be of relevance our latency
measurements must be of access mechanisms that are
adequate to support feed-forward based synchronization.
The fundamental requirement is that a counter be de-
fined, which is cumulative, wide enough to not wrap be-
tween reboots (we use 64-bit counters which take 585
years to roll over on a 1GHz processor), and accessible
from both kernel and user context. The existing ntpd-
oriented software clock mechanisms do not satisfy these
conditions. We describe below the alternatives we im-
plement.
4.1 Baseline Latencies
In this section we use the host machine kultarr, a
2.13GHz Intel Core 2 Duo, and measure access latencies
by counting the number of elapsed CPU cycles. For this
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
0.25 0.15
TSC HPET
0.2} | Min: 248 Min: 1056
Med: 320 0.1 Med: 1096
0.15} | IQR: 24 IQR: 40
0.1
0.05
0.05 | |
0! 1 | l | | boas 0
200 250 300 350 400 1050 1100 1150 1200
[CPU cycles] [CPU cycles]
XEN domo one HPET domo
0.15) | Min: 408 Min: 1576
Med: 528 7 Med: 1800
0.1, IQR: 28 0.0 IQR: 72
0.05 0.02
0 si | A alu
400 450 500 550 600 $500 2000 2500
[CPU cycles] [CPU cycles]
0.15 XEN domU|] 9.04 HPET domU
Min: 392 Min: 1544
Med: 536 0.03 Med: 1824
0.1 IQR: 32 ; IQR: 232
0.02
0.05
| 0.01
0 th I lu lou
400 500 600 700 9500 2000 2500
[CPU cycles] [CPU cycles]
Figure 5: Distribution of counter latencies. Top: TSC
and HPET in an unvirtualized system using feed-forward
compatible access; Middle: Xen Clocksource and HPET
from Dom0; Bottom: from DomU.
purpose we use the rdtsc() function, a wrapper for the
x86 RDTSC instruction to read the relevant register(s)
containing the TSC value and to return it as a 64-bit in-
teger. This provides direct access to the TSC with very
low overhead from both user and kernel space. To en-
sure a unique and reliable TSC, from the BIOS we dis-
able power management (both P-states and C-states), and
also disable the second core to avoid any potential failure
of TSC synchronization across the cores.
We begin by providing a benchmark result for HPET
on a non-virtualized system. The top right plot in Fig-
ure 5 gives its latency histogram measured from within
kernel context, using the access mechanism described in
[3]. This mechanism augments the Linux clocksource
code (which supports a choice among available hardware
counters), to expose a 64-bit cumulative version of the
selected counter. For comparison, in the top left plot we
give the latency of the TSC accessed in the same way —
it is much smaller as expected, but both counters have
low variability. Note (see [3]) that the latency of TSC
accessed directly via rdtsc() is only around 80 cycles, so
that the feed-forward friendly access mechanism entails
an overhead of around 240 cycles on this system.
USENIX Association
USENIX Association
Now consider latency in a Xen system. To provide
the feed-forward compatible access required for each of
HPET and Xen Clocksource, we first modified the Xen
Hypervisor (4.0) and the Linux kernel 2.6.31.13 (Xen
pvops branch) to expose the HPET to Dom0 and DomU.
We added a new hypercall entry that retrieves the current
raw platform timer value, HPET in this case. Like Xen
Clocksource, this is a 64-bit counter which satisfies the
cumulative requirement. We then added a system call to
enable each counter to be accessed from user context. Fi-
nally, for our purposes here we added an additional sys-
tem call that measures the latency of either HPET or Xen
Clocksource from within kernel context using rdtsc().
The middle row in Figure 5 shows the latency of Xen
Clocksource and HPET from Dom0’s kernel point of
view. The Xen Clocksource interpolation mechanism
adds an extra 200 CPU cycles compared to accessing
the TSC alone in the manner seen above, for a total of
250 ns at this CPU frequency. The HPET latency suf-
fers from the penalty created by the hypercall needed to
access it, lifting its median value by 800 cycles for a to-
tal latency of 740 ns. More importantly, we see that the
hypercall also adds more variability, with an IQR that in-
creases from 40 to 72 CPU cycles, and the appearance
of a multi-modal distribution which we speculate arises
from some form of contention among hypercalls. The
Xen Clocksource in comparison has an IQR only slightly
larger than that of the TSC counter.
The bottom row in Figure 5 shows the latency of Xen
Clocksource and HPET from DomU’s kernel point of
view. The Xen Clocksource shows the same performance
as in the Dom0 case. HPET is affected more, with an in-
crease in the number of modes and the mass within them,
resulting in a considerably increased IQR.
In conclusion, Xen Clocksource performs well despite
the overhead of its software interpolation scheme. In par-
ticular, although its latency is almost double that of a
simple TSC access (and 7 times a native TSC access), it
does not add a significant latency variability even when
accessed from DomU. On the other hand however, the
simple feed-forward compatible way of accessing HPET
used here is only four times slower than the much more
complicated Xen Clocksource and is still under 1 us.
This performance could certainly be improved, for exam-
ple by replacing the hypercall by a dedicated mechanism
such as a read-only memory-mapped interface.
4.2 Impact of Power Management
Power management is one of the key mechanisms po-
tentially affecting timekeeping. The Xen Clocksource is
designed to compensate for its effects in some respects.
Here we examine its ultimate success in terms of latency.
In this section we use the host machine sarigue, a
3GHz Intel Core 2 Duo E8400. Since we do not use
rdtsc() for latency measurement in this section, we do
not disable the second core as we did before. Instead we
measure time differences in seconds, to sub-nanosecond
precision, using the RADclock difference clock [20] with
HPET as the underlying counter”. P-states are disabled
in the BIOS, but C-states are enabled.
Ideally one would like to directly measure Xen Clock-
source’s interpolation mechanism and so evaluate it in
detail. However, the Xen Clocksource recalibrates the
HPET interpolation on every change in P-State (fre-
quency changes) or C-State (execution interruption), as
well as once per second. Since oscillations for exam-
ple between C-States occur hundreds of times per second
[8], it is not possible to reliably timestamp these events,
forcing us to look at coarser characterizations of perfor-
mance.
From the timestamping perspective, the main problem
is the obvious additional latency due to the computer be-
ing idle in a C-State when an event to timestamp occurs.
For example, returning from C-State C3 to execution CO
takes about 20 us [8].
For the purpose of synchronization over the network,
the timestamping of outgoing and incoming synchroniza-
tion packets is of particular interest. A useful measure of
these is the Round-Trip-Time (RTT) of a request-reply
exchange, however since this includes network queuing
and delays at the time server as well as delays in the host,
it is of limited use in isolating the latter.
To observe the host latency we introduce a metric we
call the RTThost, which is roughly speaking the compo-
nent of the RTT that lies within the host. More precisely,
for a given request-reply packet pair, the RTThost is the
sum of the two one-way delays from the host to the DAG
card, and from the DAG card to the host. It is not possible
to reliably measure these one-way delays individually in
our testbed. However, the RTThost can be reliably mea-
sured as the difference of the RTT seen by the host and
that seen by the DAG card. The RTThost is a measure
of ‘system noise’ with a specific focus on packet times-
tamping. The smaller RTThost, the less noisy the host,
and the higher the quality of packet timestamps.
Figure 6 shows RTThost values measured over 80
hours on two DomU guests on the same host. The cap-
ture starts with only the C-State CO enabled, that is with
power management functions disabled. Over the period
of the capture, deeper C-States are progressively enabled
and we observe the impact on RTThost. At each stage
the CPU moves between the active state CO and the idle
states enabled at the time. Table 1 gives a breakdown of
2This level of precision relates to the difference clock itself, when
measuring time differences of size of the order of 100 us as here. It
does not take into account the separate issue of timestamping errors,
such as the (much larger!) counter access latencies studied above.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 710) 457
458
100;
4110} Xen Clocksource v4
—HPET Hypervisor '
S
1
|
RTT Host [us]
40 50 60 70 80
mw 90 é i
= a,
8 80 a :
ao i
E 70 7 = ae a
E ' ' 4 2100
re 1 ! ak BD
60 yoo L =
oe a ~ -
a Z 50.
“ 0
Co Ci C2 C3
30. 40 50 60 70 80
Time [hour]
Figure 6: System noise as a function of the deepest enabled C-State for Xen Clocksource (upper time series and left
box plots) and HPET (lower time series and right box plots). Time series plots have been sampled for clarity.
time spend in different states. It shows that typically the
CPU will rest in the deepest allowed C-state unless there
is a task to perform.
The left plot in Figure 6 is a compact representation of
the distribution of RTThost values for Xen Clocksource
and HPET, for each section of the corresponding time se-
ries presented on the right of the figure. Here whiskers
show the minimum and 99th percentile values, the lower
and upper sides of the box give the 25th and 75th per-
centiles, while the internal horizontal line marks the me-
dian.
The main observation is that, for each counter, RT-
Thost generally increases with the number of C-States
enabled, although it is slightly higher for HPET. The in-
crease in median RTThost from CO to C3 is about 20 us, a
value consistent with [8]. The minimum value is largely
unaffected however, consistent with the fact that if a
packet (which of course is sent when the host is in CO),
is also received when it is in CO, then it would see the
RTThost corresponding to CO, even if it went idle in be-
tween.
We saw earlier that the access latencies of HPET and
Xen Clocksource differ by less than 1 us, and so this can-
not explain the differences in their RTThost median val-
ues seen here for each given C-State. These are in fact
due to the slightly different packet processing in the two
DomU systems.
Co Cl C2 C3
CO enabled | 100% - - -
Cl enabled | 2.17% | 97.83% - -
C2 enabled | 2.85% | 0.00% | 97.15% -
C3 enabled | 2.45% | 0.00% 1.84% | 95.71%
Table 1: Residency time in different C-States. Here “Cn
enabled” denotes that all states from CO up to Cn are
enabled.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
We conclude that Xen Clocksource, and HPET using
our proof of concept access mechanism, are affected by
power management when it comes to details of times-
tamping latency. These translate into timestamping er-
rors, which will impact both clock reading and poten-
tially clock synchronization itself. The final size of
such errors however is also crucially dependent on the
asymmetry value associated to RTThost, which is un-
known. Thus the RTTHost measurements effectively
place a bound on the system noise affecting timestamp-
ing, but do not determine it.
5 New Architecture for Virtualized Clocks
In this section we examine the performance and detail
the benefits of the RADclock algorithm in the Xen en-
vironment, describe important packet timestamping is-
sues which directly impact clock performance, and fi-
nally propose a new feed-forward based clock architec-
ture for para-virtualized systems.
In Section 5.1 we use sarigue, and in Section 5.2 kul-
tarr, with the same BIOS and power management set-
tings described earlier.
5.1 Independent RADclock Performance
We begin with a look at the performance of the RAD-
clock in a Xen environment. Figure 7 shows the final er-
ror of two independent RADclocks, one using HPET and
the other Xen Clocksource, running concurrently in two
different DomU guests. Separate NTP packet streams
are used to the same Stratum-1 server on the LAN with
a poll period of 16 seconds. The clock error for each
clock has been corrected for path asymmetry, in order
to reveal the underlying performance of the algorithm as
a delay variability filter (this is possible in our testbed,
but impossible for the clocks in normal operation). The
difference of median values between the two clocks is
USENIX Association
USENIX Association
x 10°
q 20
5 10H ws WA nt -
me 0 AA a AN hu A IN JN él A\ IM rs
gy yw ly
A- SeR [arpa phan aie sincc |. 7 Se Clocksourcel
< ——HPET
ao ~20 L L L i
0 50 100 150 200 250 300 350 ~10 0 10 -10 0 10
Time [mn] RADclock error [1s] RADclock error [1s]
Figure 7: RADclock performance in state CO using Xen Clocksource and HPET, running in parallel, each in a separate
DomU guest.
extremely small, and below the detection level of our
methodology. We conclude that the clocks essentially
have identical median performance.
In terms of clock stability, as measured by the IQR of
the clock errors, the two RADclock instances are again
extremely similar, which reinforces our earlier observa-
tions that the difference in stability of the Xen Clock-
source and HPET is very small (below the level of detec-
tion in our testbed), and that RADclock works well with
any appropriate counter. The low frequency oscillation
present in the time series here is due to the periodic cy-
cle of the air conditioning system in the machine room,
and affects both clocks in a similar manner consistent
with previous results [3]. It is clearly responsible for the
bulk of the RADclock error in this and other experiments
shown in this paper.
Power management is also an important factor that
may impact performance. Figure 8 shows the distribu-
tion of clock errors of the RADclock, again using HPET
and the Xen Clocksource separately but concurrently as
above, with different C-State levels enabled. In this case
the median of each distribution has simply been shifted
to zero to ease the stability (IQR) comparison. For each
of the C-State levels shown, the stability of the RADclock
is essentially unaffected by the choice of counter.
As shown in Figure 6, power management creates ad-
ditional delays of higher variability when timestamping
timing packets exchanged with the reference clock. The
near indifference of the IQR given in Figure 8 to C-State
shows that the RADclock filtering is robust enough to see
through this extra noise.
Power management also has an impact on the asym-
metry error all synchronization algorithms must face. In
an excellent example of systematic observation bias, in
a bidirectional paradigm a packet send by an OS would
not be delayed by the power management strategy, be-
cause the OS chooses to enter an idle state only when it
has nothing to do. On the other hand, over the time in-
terval defined by the RTT of a time request, it is likely
the host will choose to stop its execution and enter an
idle state (perhaps a great many times) and the return-
ing packet may find the system in such a state. Con-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
20/[—RADclock Xen | 7]
@ 4n[[—RADclock HPET| as. = 4
2 15 = a 7
Ga
= 10
s
8 5
5
w 0
8
wu -5 ‘
¥
© -10 ‘
o 1 1
2-15 -%
co ~ ~ 1
a+ ri
-20 1 1 1 1 ad
co C1 c2 c3
Figure 8: Compact centred distributions of RADclock
performance as a function of the deepest C-State enabled
(whiskers give 1st to 99th percentile).
sequently, only the timestamping of received packets is
likely to be affected by power management, which trans-
lates into a bias towards an extra path asymmetry, in the
sense of ‘most but not all packets’, in the receiving direc-
tion. This bias is difficult to measure independently and
authoritatively. The measurement of the RTThost shown
in Figure 6 gives however a direct estimate of an upper
bound for it.
5.2 Sharing the Network Card
The quality of the timestamping of network packets is
crucial to the accuracy the synchronization algorithm can
achieve. The networking in Xen relies on a firewall and
networking bridge managed by Dom0. In Figure 9 we
observe the impact of system load on the performance of
this mechanism.
The top plot shows the RTThost time series, as seen
from the Dom0 perspective, as we add more DomU
guests to the host. Starting with Dom0 only, we add an
additional guest every 12 hours. None of the OSs run
any CPU or networking intensive tasks. The middle plot
gives the box plots of the time series above, where the
increase in median and IQR values is more clearly seen.
For reference the ‘native’ RTThost of a non-virtualized
system is also plotted. The jump from this distribution
459
460
to the one labeled ‘Dom0’ represents the cost of the net-
working bridge implemented in Xen.
The last plot in figure 9 shows the distribution of RT-
Thost values from each guest’s perspective. All guests
have much worse performance than Dom0, but perfor-
mance degrades by a similar amount as Dom0 as a func-
tion of the number of guests. For a given guest load
level, the performance of each guest clock seems essen-
tially the same, though with small systematic differences
which may point to scheduling policies.
The observations above call for the design of a
timestamping system under a dependent clock paradigm
where Dom0 has an even higher priority in terms of net-
working, so that it can optimize its timestamping qual-
ity and thereby minimize the error in the central Dom0
clock, to the benefit of all clocks on the system. Fur-
ther, DomU packet timestamping should be designed to
minimize any differences between DomU guests, and re-
duce as much as possible the difference in host asym-
metry between Dom0 and DomU guests, to help make
the timestamping performance across the whole system
more uniform.
407 | [1 guest | 2 guests 3 guests | 4 guests] 7
0 12 24 36 48 60
Time [hour]
70 me as . a
a
260 _ |} =
8 ———
= 50 SS 1 ! _
E 40 ze gs
oc
30) ==
Native Dom0O 1 guest 2 guests 3 guests 4 guests
—DomU #1) ea ee ta
@°00/—Domu #2) | Pi ae
= —DomuU #3) | | ror be eG
8 —DomU #4] |! a ae ae
£ 150 a ca 2 m4
E “ of yy ne i
Eo, 88 866 9Beb
1 guest 2 guests 3 guests 4 guests
Figure 9: kultarr: RTThost (a.k.a. system noise) as a
function of the number of active guests. Top: RTThost
timeseries seen by Dom0; Middle: corresponding dis-
tribution summaries (with native non-Xen case added on
the left for comparison); Bottom: as seen by each DomU.
Whiskers show the minimum and 99th percentile.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
5.3. A Feed-Forward Architecture
As described in Section 2.5, the feed-forward approach
used by the RADclock has the advantage of cleanly
separating timestamping (performed as a raw times-
tamp in the kernel or user space as needed), which is
stateless, and the clock synchronization algorithm itself,
which operates asynchronously in user space. The algo-
rithm updates clock parameters and makes them avail-
able through the OS, where any authorized clock reading
function (a kind of almost trivial stateless “system clock’)
can pick them up and use them either to compose an ab-
solute timestamp, or robustly calculate a time difference
[20].
The RADclock is then naturally suited for the depen-
dent clock paradigm and can be implemented in Xen as a
simple read/write stateless operation using the XenStore,
a file system that can be used as an inter-OS communica-
tion channel. After processing synchronization informa-
tion received from its time server, the RADclock running
on Dom0 writes its new clock parameters to the Xen-
Store. On DomU, a process reads the updated clock pa-
rameters upon request and serves them to any application
that needs to timestamp events. The application times-
tamps the event(s) of interest. These raw timestamps can
then be easily converted either into a wallclock time or
a time difference measured in seconds (this can even be
done later off-line).
Unlike with ntpd and its coupled relationship to the
(non-trivial) incumbent system clock code, no adjust-
ment is passed to another dynamic mechanism, which
ensures that only a single clock, clearly defined in a sin-
gle module, provides universal time across Dom0 and all
DomU guests.
With the above architecture, there is only one way in
which a guest clock can not be strictly identical with the
central Dom0 clock. The read/write operation on the
XenStore is not instantaneous and it is possible that the
update of clock parameters, which is slightly delayed af-
ter the processing of a new synchronization input to the
RADclock, will result in different parameters being used
to timestamp some event. In other words, the time across
OSs may appear different for a short time if a timestamp-
ing function in a DomU converts a raw timestamp with
outdated data. However, this is a minor issue since clock
parameters change slowly, and using out of date values
has the same impact as the synchronization input simply
being lost, to which the clock is already robust.
In Figure 10 we measured the time required to write to
the XenStore using the RADclock difference clock which
has an accuracy well below | us [20]. We present results
obtained on 2 host machines with slightly different hard-
ware architectures, namely kultarr (2.13 GHz Intel Core
2 Duo) and tastiger (3.40 GHz Intel Pentium D), that
USENIX Association
USENIX Association
Kultarr
Med: 1234.1
IQR: 202.3
Tastiger
Med: 1440.2
IQR: 49.0
1800 2000 a 1000 1200 1400 1600
XenStore latency [1s]
1400. 1600
XenStore latency [ts]
Figure 10: Distribution of clock update latency through
the xenstore, tastiger (left, Pentium D, 3.4GHz) and kul-
tarr (right, Core 2 Duo, 2.13GHz).
show respective median delays of 1.2 and 1.4ms. As-
suming a 16s poll period, this corresponds to 1 chance
out of 11,500 that the clocks would (potentially) disagree
if read at some random time.
The dependent RADclock is ideally suited for time
keeping on Xen DomU. It is a simple, stateless, standard
read/write operation that is robust as it avoids the danger-
ous dynamics of feedback approaches, ensures that the
clocks of all guests agree, and is robust to system load
and power management effects. As a dependent clock
solution, it saves both host and network resources and
is inherently scalable. Thanks to a simple timestamping
function it provides the same level of final timekeeping
accuracy to all OSs.
6 A Migration-Friendly Architecture
Seamlessly migrating a running system from one phys-
ical machine to another is a key innovation of virtu-
alization [13, 7]. However this operation becomes far
from seamless with respect to timing when using ntpd.
As mentioned in Section 3, ntpd’s design requires each
DomU to run its own instance of the ntpd daemon, which
is fundamentally unsuited to migration, as we now ex-
plain.
The synchronization algorithm embodied in the ntpd
daemon is stateful. In particular it maintains a time vary-
ing estimate of the Xen Clocksource’s rate-of-drift and
current clock error, which in turn is defined by the char-
acteristics of the oscillator driving the platform counter.
After migration, the characteristics seen by ntpd change
dramatically since no two oscillators drift in the same
way. Although the Xen Clocksource counters on each
machine nominally share the same frequency (1GHz), in
practice this is only true very approximately. The tem-
perature environment of the machine DomU migrates to
can be very different from the previous one which can
have a large impact, but even worse, the platform timer
may be of a different nature, HPET originally and ACPI
after migration for example. Furthermore, ntpd will also
inevitably suffer from an inability to account for the time
during which DomU has been halted during the migra-
tion. When DomU restarts, the reference wallclock time
and last Xen Clocksource value maintained by its system
clock will be quite inconsistent with the new ones, lead-
ing to extreme oscillator rate estimates. In summary, the
sudden change in status of ntpa’s state information, from
valid to almost arbitrary, will, at best, deliver a huge error
immediately after migration, which we expect to decay
only slowly according to ntpd’s usual slow convergence.
At worst, the ‘shock’ of migration may push nfpd into an
unstable regime from which it may never recover.
In contrast, by decomposing the time information into
raw timestamps and clock parameters, as described in
Section 5, the RADclock allows the daemon running on
DomU to be stateless within an efficient dependent clock
strategy. The migration then becomes trivial from a time-
keeping point of view. Once migrated, DomU times-
tamps events of interests with its chosen counter and re-
trieves the RADclock clock parameters maintained by the
new Dom0 to convert them into absolute time. DomU
immediately benefits from the accuracy of the dedicated
RADclock running on Dom0 — the convergence time is
effectively zero.
The plots in Figure 11 confirm the claims above and il-
lustrate a number of important points. In this experiment,
each of tastiger and kultarr run an independent RAD-
clock in Dom0. The clock error for these is remarkably
similar, with an IQR below 10 us as seen in the top plot
(measured using the DAG external comparison). Here
for clarity the error time series for the two Dom0 clocks
have been corrected for asymmetry error, thereby allow-
ing their almost zero inherent median error, and almost
identical behavior (the air-conditioning generated oscil-
lations overlay almost perfectly), to be clearly seen.
For the migration experiment, a single DomU OS is
started on tastiger, and two clocks launched on it: a de-
pendent RADclock, and an independent ntpd clock. A
few hours of warm up are then given (not shown) to al-
low nipd to fully converge. The experiment proper then
begins. At the 30 minute mark DomU is migrated to kul-
tarr, it migrates back to tastiger after 2 hours then back
again after another 2, followed by further migrations with
a smaller period of 30 minutes.
The resulting errors of the two migrating DomU
clocks are shown in the top plot, and in a zoomed out
version in the middle plot, as measured using the ex-
ternal comparison. Before the results, a methodologi-
cal point. The dependent RADclock running on DomU
is by construction identical to the RADclock running on
Dom0, and so the two time series (if asymmetry cor-
rected) would superimpose almost perfectly, with small
differences owing to the different errors in the times-
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI’10) 461
462
250F c
| —— Dom0 - Tastiger
200+ | — Dom0 - Kultarr |
g —— Migrated Guest RADclock
5 150 - | ——— Migrated Guest ntpd |
5 |
S tf, L ny HW Thafih
5 hte cil inti — TT Pht jad
oO
0 pear ae re ey nel ates ry oA At a
|
-50E | | | | | 4
0 1 2 3 4 5
Time [Hours]
30
ae ntpd
20; 7
S 15; 4
oO
107 -
oO
O 5- 7
5 | ! ! ! |
0 1 2 3 4 5 6
Time [Hours]
I
50 ntpd | : ! : J
— 404. —— RADclock | |
q 7 I
= 30 7
©
o 20 W hy r |
8 104 PVN AN i Ag yh A ly Ate ‘| AN PA
a «4 A Pye AA A ry ry ry A PH yy
|
~10-b ! , | i : |
| | | I | | lL |
0 1 2 3 4 5
Time [Hours]
Figure 11: Clock errors under migration. Top: asymmetry corrected unmigrated RADclock Dom0 clocks, and (uncor-
rected) migrated clocks on DomU; Middle: zoom out on top plot revealing the huge size of the migration ‘shock’ on
ntpd; Bottom: effect of migration load on Dom0 clocks on kultarr.
tamping of the separate UDP packet streams. We choose
however, in the interests of fairness and simplicity of
comparison, not to apply the asymmetry correction in
this case, since it is not possible to apply an analogous
correction to the ntpd error time series. As a substi-
tute, we instead draw horizontal lines over the migrating
RADclock time series representing the correction which
would have been applied. No such lines can be drawn in
the ntpd case.
Now to the results. As expected, and from the very
first migration, ntpd exhibits extremely large errors (from
-1 to 27s!) for periods exceeding 15 minutes (see zoom
in middle plot) and needs at least another hour to con-
verge to a reasonable error level. The dependent RAD-
clock on the other hand shows seamless performance
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
with respect to the horizontal lines representing the ex-
pected jumps due to asymmetry changes as just de-
scribed. These jumps are in any case small, of the order
of a few microseconds. Note that these corrections are
a function both of RTThost and asymmetry that are both
different between tastiger and kultarr.
Finally, we present a load test comparison. The bottom
plot in Figure 11 compares in detail the performance of
the independent RADclock running on Dom0 on kultarr,
and an independent ntpd clock, also running on Dom0
during the experiment (not shown previously). Whereas
the RADclock is barely affected by the changes in net-
work traffic and system load associated with the migra-
tions of the DomU guest, ntpd shows significant devi-
ation. In summary, not only is ntpd in an independent
USENIX Association
USENIX Association
clock paradigm incompatible with clock migration, it is
also, regardless of paradigm, affected by migration oc-
curring around it.
One could also consider the performance of an inde-
pendent RADclock paradigm under migration. However,
we expect that the associated ‘migration shock’ would be
severe as the RADclock is not designed to accommodate
radical changes in the underlying counter. Since the de-
pendent solution is clearly superior from this and many
other points of view, we do not present results for the
independent case under migration.
7 Conclusion
Virtualization of operating systems and accurate com-
puter based timing are two areas set to increase in im-
portance in the future. Using Xen para-virtualization as
a concrete framework, we highlighted the weaknesses
of the existing timing solution, which uses indepen-
dent ntpd synchronization algorithms (coupled to state-
ful software clock code) for each guest operating system.
In particular, we showed that this solution is fundamen-
tally unsuitable for the important problem of live VM
migration, using both arguments founded on the design
of ntpd, as well as detailed experiments in a hardware-
validated testbed.
We reviewed the architecture of the RADclock algo-
rithm, in particular its underlying feed-forward basis, the
clean separation between its timestamping and synchro-
nization aspects, and its high robustness to network and
system noise (latency variability). We argued that these
features make it ideal as a dependent clock solution, par-
ticularly since the clock is already set up to be read
through combining a raw hardware counter timestamp
with clock parameters sourced from a central algorithm
which owns all the synchronization intelligence, via a
commonly accessible data structure. We supported our
claims by detailed experiments and side-by-side com-
parisons with the status quo. For the same reasons, the
RADclock approach enables seamless and simple migra-
tion, which we also demonstrated in benchmarked ex-
periments. The enabling of a dependent clock approach
entails considerable scalability advantages and suggests
further improvements through optimizing the timestamp-
ing performance of the central clock in Dom0.
As part of an examination of timestamping and
counter suitability for timekeeping in general and the
feed-forward paradigm in particular, we provided a de-
tailed evaluation of the latency and accuracy of the Xen
Clocksource counter, and compared it to HPET. We con-
cluded that it works well as intended, however note that
it is a complex solution created to solve a problem which
will soon disappear as reliable TSC counters again be-
come ubiquitous. The RADclock is suitable for use with
any counter satisfying basic properties, and we showed
its performance using HPET or Xen Clocksource was in-
distinguishable.
The RADclock [14] packages for Linux now support a
streamlined version of the architecture for Xen described
here using Xen Clocksource as the hardware counter.
With the special code allowing system instrumentation
and HPET access removed, no modifications to the hy-
pervisor are finally required.
8 Acknowledgments
The RADclock project is partially supported under Aus-
tralian Research Council’s Discovery Projects funding
scheme (project number DP0985673) and a Google Re-
search Award.
We thank the anonymous reviewers and our shepherd
for their valuable feedback.
9 Availability
RADclock packages for Linux and _ FreeBSD,
software and papers, can be found at
http://www.cubinlab.ee.unimelb.edu.au/radclock/.
References
[1] Xen.org History. http://www.xen.org/community/
xenhistory.html.
[2] BARHAM, P., DRAGOVIC, B., FRASER, K., HAND, S.,
HARRIS, T., HO, A., NEUGEBAUER, R., PRATT, I., AND
WARFIELD, A. Xen and the Art of Virtualization. In SOSP ’03:
Proceedings of the nineteenth ACM symposium on Operating sys-
tems principles (New York, NY, USA, 2003), ACM, pp. 164-177.
BROOMHEAD, T., RIDOUX, J., AND VEITCH, D. Counter
Availability and Characteristics for Feed-forward Based Synchro-
nization. In Int. IEEE Symp. Precision Clock Synchronization for
Measurement, Control and Communication (ISPCS’09) (Brescia,
Italy, Oct. 12-16 2009), IEEE Piscataway, pp. 29-34.
ENDACE. Endace Measurement Systems. DAG series PCI and
PCI-X cards. http://www.endace.com/networkMCards.htm.
[3
oS
[4
&
[S
=
INTEL CORPORATION. JA-PC HPET (High Precision Event
Timers) Specification (revision 1.0a). http://www.intel.
com/hardwaredesign/hpetspec_1.pdf, Oct. 2004.
[6
So
KAMP, P. H. Timecounters: Efficient and precise timekeeping
in SMP kernels. In Proceedings of the BSDCon Europe 2002
(Amsterdam, The Netherlands, 15-17 November 2002).
KEIR, C. C., CLARK, C., FRASER, K., H, S., HANSEN,
J. G., JUL, E., LIMPACH, C., PRATT, I., AND WARFIELD, A.
Live Migration of Virtual Machines. In Proceedings of the 2nd
ACM/USENIX Symposium on Networked Systems Design and Im-
plementation (NSDI) (2005), pp. 273-286.
KIDD, T. Intel Software Network Blogs. http: //software.
intel.com/en-us/blogs/author/taylor-kidd/.
[7
oS
[8
oo
[9
=
MENON, A., SANTOS, J. R., TURNER, Y., JANAKIRAMAN,
G. J., AND ZWAENEPOEL, W. Diagnosing performance over-
heads in the xen virtual machine environment. In VEE ’05:
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI 10) 463
464
[10]
(11)
[12]
[13]
[14]
(15)
[16]
[17]
[18]
[19]
[20]
[21]
Proceedings of the Ist ACM/USENIX international conference
on Virtual execution environments (New York, NY, USA, 2005),
ACM, pp. 13-23.
MICROSOFT CORPORATION. Guidelines For Providing Multi-
media Timer Support. Tech. rep., Microsoft Corporation, Sep.
2002. http://www.microsoft.com/whdc/system/
sysinternals/mm-timer.mspx.
MILLS, D. L. Computer Network Time Synchronization: The
Network Time Protocol. CRC Press, Inc., Boca Raton, FL, USA,
2006.
MOGUL, J., MILLS, D., BRITTENSON, J., STONE, J., AND
WINDL, U. Pulse-Per-Second API for UNIX-like Operating Sys-
tems, Version 1.0. Tech. rep., IETF, 2000.
NELSON, M., HONG LIM, B., AND HUTCHINS, G._ Fast
transparent migration for virtual machines. In Proceedings of
the annual conference on USENIX Annual Technical Conference
(2005), USENIX Association.
RIDOUX, J., AND VEITCH, D. RADclock Project webpage.
RIDOUX, J., AND VEITCH, D. A Methodology for Clock Bench-
marking. In Tridentcom (Orlando, FL, USA, May 21-23 2007),
IEEE Comp. Soc.
RIDOUX, J., AND VEITCH, D. The Cost of Variability. In Int.
IEEE Symp. Precision Clock Synchronization for Measurement,
Control and Communication (ISPCS’08) (Ann Arbor, Michigan,
USA, Sep. 24-26 2008), pp. 29-32.
RIDOUX, J., AND VEITCH, D. Ten Microseconds Over LAN, for
Free (Extended). JEEE Trans. Instrumentation and Measurement
(TIM) 58, 6 (June 2009), 1841-1848.
RIDOUX, J., AND VEITCH, D. Principles of Robust Timing Over
the Internet. ACM Queue, Communications of the ACM 53, 5
(May 2010), 54-61.
THE XEN TEAM. Xen Documentation. http: //www.xen.
org/files/xen_interface.pdf.
VEITCH, D., RIDOUX, J., AND KORADA, S. B. Robust Syn-
chronization of Absolute and Difference Clocks over Networks.
IEEE/ACM Transactions on Networking 17, 2 (April 2009), 417—
430.
VMWARE. Timekeeping in VMware Virtual Machines. Tech.
rep., VMware, May 2010. http://www. vmware.com/
files/pdf/Timekeeping-In-VirtualMachines.
pdf.
9th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’10)
USENIX Association